diff options
298 files changed, 59927 insertions, 5048 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-rtc-rtc0-device-rtc_calibration b/Documentation/ABI/testing/sysfs-class-rtc-rtc0-device-rtc_calibration new file mode 100644 index 00000000000..ae217d4a3a6 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-rtc-rtc0-device-rtc_calibration @@ -0,0 +1,13 @@ +What: Attribute for calibrating ST-Ericsson AB8500 Real Time Clock +Date: Oct 2011 +KernelVersion: 3.0 +Contact: Mark Godfrey <mark.godfrey@stericsson.com> +Description: The rtc_calibration attribute allows the userspace to + calibrate the AB8500.s 32KHz Real Time Clock. + Every 60 seconds the AB8500 will correct the RTC's value + by adding to it the value of this attribute. + The range of the attribute is -127 to +127 in units of + 30.5 micro-seconds (half-parts-per-million of the 32KHz clock) +Users: The /vendor/st-ericsson/base_utilities/core/rtc_calibration + daemon uses this interface. + diff --git a/Documentation/ABI/testing/sysfs-socinfo b/Documentation/ABI/testing/sysfs-socinfo new file mode 100644 index 00000000000..afd9da2fa76 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-socinfo @@ -0,0 +1,16 @@ +What: /sys/socinfo +Date: March 2011 +contact: Maxime Coquelin <maxime.coquelin-nonst@stericsson.com> +Description: + The /sys/socinfo directory contains information about the + System-on-Chip. It is only available if platform implements it. + This directory contains two kind of attributes : + - common attributes: + * machine: the name of the machine. + * family: the family name of the SoC + - SoC-specific attributes: The SoC vendor can declare attributes + to export some strings to user-space, like the serial-number for + example. + +Users: + User-space applications which needs these kind of attributes. diff --git a/Documentation/DocBook/Makefile b/Documentation/DocBook/Makefile index 66725a3d30d..80c42603ff2 100644 --- a/Documentation/DocBook/Makefile +++ b/Documentation/DocBook/Makefile @@ -14,7 +14,12 @@ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \ genericirq.xml s390-drivers.xml uio-howto.xml scsi.xml \ 80211.xml debugobjects.xml sh.xml regulator.xml \ alsa-driver-api.xml writing-an-alsa-driver.xml \ - tracepoint.xml drm.xml media_api.xml + tracepoint.xml drm.xml media_api.xml \ + i2s.xml msp.xml shrm.xml stmpe.xml touchp.xml \ + tc_keypad.xml prcmu-fw-api.xml cg2900_fm_radio.xml \ + synaptics_rmi4_touchp.xml db5500_keypad.xml \ + u5500_LogicalMailbox.xml cg2900.xml \ + lsm303dlh.xml ste_ff_vibra.xml ux500_usb.xml include $(srctree)/Documentation/DocBook/media/Makefile diff --git a/Documentation/DocBook/device-drivers.tmpl b/Documentation/DocBook/device-drivers.tmpl index b638e50cf8f..5f70f734e8b 100644 --- a/Documentation/DocBook/device-drivers.tmpl +++ b/Documentation/DocBook/device-drivers.tmpl @@ -437,4 +437,21 @@ X!Idrivers/video/console/fonts.c !Edrivers/i2c/i2c-core.c </chapter> + <chapter id="hsi"> + <title>High Speed Synchronous Serial Interface (HSI)</title> + + <para> + High Speed Synchronous Serial Interface (HSI) is a + serial interface mainly used for connecting application + engines (APE) with cellular modem engines (CMT) in cellular + handsets. + + HSI provides multiplexing for up to 16 logical channels, + low-latency and full duplex communication. + </para> + +!Iinclude/linux/hsi/hsi.h +!Edrivers/hsi/hsi.c + </chapter> + </book> diff --git a/Documentation/DocBook/gpio.tmpl b/Documentation/DocBook/gpio.tmpl new file mode 100644 index 00000000000..b69c2770210 --- /dev/null +++ b/Documentation/DocBook/gpio.tmpl @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" []> + +<book id="GPIO"> + <bookinfo> + <title>GPIO1B</title> + + <authorgroup> + <author> + <firstname>Alessandro</firstname> + <surname>Rubini</surname> + <affiliation> + <address> + <email>rubini@unipv.it</email> + </address> + </affiliation> + </author> + <author> + <firstname>Prafulla</firstname> + <surname>WADASKAR</surname> + <affiliation> + <address> + <email>prafulla.wadaskar@st.com</email> + </address> + </affiliation> + </author> + </authorgroup> + + <copyright> + <year>2008-2010</year> + <holder>ST-Ericsson</holder> + </copyright> + + <subjectset> + <subject> + <subjectterm>Linux standard functions</subjectterm> + </subject> + </subjectset> + + <legalnotice> + <!-- Do NOT remove the legal notice below --> + + <para> + This documentation 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. + </para> + + <para> + 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. + </para> + + <para> + 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 + </para> + + <para> + For more details see the file COPYING in the source + distribution of Linux. + </para> + </legalnotice> + </bookinfo> + +<toc></toc> + <chapter id="intro"> + <title>Introduction</title> + <para> + This Documentation describes the API's provided by the GPIO controller Driver. + </para> + <para> + Only the API specific to the Ux500 platform is listed here. For the generic GPIO + API, see <filename>Documentation/gpio.txt</filename> in the kernel source tree. + </para> + </chapter> + + <chapter id="bugs"> + <title>Known Bugs And Assumptions</title> + <para> + <variablelist> + <varlistentry> + <term>None</term> + <listitem> + <para> + None. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </chapter> + + <chapter id="pubfunctions"> + <title> Public Interface </title> + <para> + This Section lists the API's provided by the GPIO controller driver to client drivers. + </para> + <para> + Only the API specific to the Ux500 platform is listed here. For the generic GPIO + API, see <filename>Documentation/gpio.txt</filename> in the kernel source tree. + </para> +!Earch/arm/plat-nomadik/gpio.c + </chapter> +</book> diff --git a/Documentation/DocBook/i2c.tmpl b/Documentation/DocBook/i2c.tmpl new file mode 100644 index 00000000000..8a4cb49204e --- /dev/null +++ b/Documentation/DocBook/i2c.tmpl @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" []> + +<book id="I2C"> + <bookinfo> + <title>I2C</title> + + <authorgroup> + <author> + <firstname>Srinidhi</firstname> + <surname>Kasagar</surname> + <affiliation> + <address> + <email>srinidhi.kasagar@stericsson.com</email> + </address> + </affiliation> + </author> + <author> + <firstname>Sachin</firstname> + <surname>Verma</surname> + <affiliation> + <address> + <email>sachin.verma@st.com</email> + </address> + </affiliation> + </author> + </authorgroup> + + <copyright> + <year>2009-2010</year> + <holder>ST-Ericsson</holder> + </copyright> + + <subjectset> + <subject> + <subjectterm>Linux standard functions</subjectterm> + </subject> + </subjectset> + + <legalnotice> + <!-- Do NOT remove the legal notice below --> + + <para> + This documentation 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. + </para> + + <para> + 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. + </para> + + <para> + 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 + </para> + + <para> + For more details see the file COPYING in the source + distribution of Linux. + </para> + </legalnotice> + </bookinfo> + +<toc></toc> + <chapter id="intro"> + <title>Introduction</title> + <para> + This Documentation describes the API's provided by the I2C controller Driver. + Since this driver registers the transferfunction with kernel framework, there + are only private functions in this I2C bus driver. This driver currently + works only in master mode and does 7 bit adderssing only. There is no support + for 10 bit addressing. The driver currently supports standard mode (100KHz) + and Fast mode (400KHz) operation. + </para> + </chapter> + <chapter id="bugs"> + <title>Known Bugs And Assumptions</title> + <para> + <variablelist> + <varlistentry> + <term>None</term> + <listitem> + <para> + None. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </chapter> + + <chapter id="pubfunctions"> + <title>Public Functions Provided</title> + <para> + Not Applicable + </para> + </chapter> + + <chapter id="private"> + <title>Private Functions</title> + <para> + This Section lists the functions used internally by the I2C controller driver. + </para> +!Idrivers/i2c/busses/i2c-nomadik.c + </chapter> + +</book> diff --git a/Documentation/DocBook/prcmu-fw-api.tmpl b/Documentation/DocBook/prcmu-fw-api.tmpl new file mode 100644 index 00000000000..445a277933c --- /dev/null +++ b/Documentation/DocBook/prcmu-fw-api.tmpl @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" []> + +<book id="STw4500"> + <bookinfo> + <title>PRCMU Driver</title> + + <authorgroup> + <author> + <firstname>Sudeep Karkada</firstname> + <surname>Nagesha</surname> + <affiliation> + <address> + <email>sudeepkarkada.nagesha@stericsson.com</email> + </address> + </affiliation> + </author> + </authorgroup> + + <copyright> + <year>2009-2010</year> + <holder>ST-Ericsson</holder> + </copyright> + + <subjectset> + <subject> + <subjectterm>Linux standard functions</subjectterm> + </subject> + </subjectset> + + <legalnotice> + <!-- Do NOT remove the legal notice below --> + + <para> + This documentation 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. + </para> + + <para> + 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. + </para> + + <para> + 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 + </para> + + <para> + For more details see the file COPYING in the source + distribution of Linux. + </para> + </legalnotice> + </bookinfo> + +<toc></toc> + + <chapter id="intro"> + <title>Introduction</title> + <para> + This documentation describes the API provided by the PRCMU firmware interface driver. + </para> + </chapter> + + <chapter id="bugs"> + <title>Known Bugs And Assumptions</title> + <para> + <variablelist> + <varlistentry> + <term>None</term> + <listitem> + <para> + None. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </chapter> + + <chapter id="enum"> + <title>Enumerations</title> + <para> + This chapter contains the autogenerated documentation of the structures + and enumerations which are used in the PRCMU firmware interface driver. + It is also required by the client drivers. + </para> +!Iinclude/linux/mfd/dbx500-prcmu.h + </chapter> + + <chapter id="pubfunctions"> + <title>Public Functions Provided</title> + <para> + This chapter contains the autogenerated documentation of the kernel + API functions which are exported to the client drivers. + </para> +!Edrivers/mfd/db8500-prcmu.c + </chapter> + + + </book> diff --git a/Documentation/DocBook/stmpe.tmpl b/Documentation/DocBook/stmpe.tmpl new file mode 100644 index 00000000000..9e64a00f6b3 --- /dev/null +++ b/Documentation/DocBook/stmpe.tmpl @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" []> + +<book id="STMPE MFD devices"> + <bookinfo> + <title>STMPE IO-Port Expander guide</title> + + <authorgroup> + <author> + <firstname>Rabin</firstname> + <surname>Vincent</surname> + <affiliation> + <address> + <email>rabin.vincent@stericsson.com</email> + </address> + </affiliation> + </author> + </authorgroup> + + <copyright> + <year>2010</year> + <holder>ST-Ericsson</holder> + </copyright> + + <subjectset> + <subject> + <subjectterm>Linux standard functions</subjectterm> + </subject> + </subjectset> + + <legalnotice> + <para> + This documentation is free software; you can redistribute + it and/or modify it under the terms of the GNU General Public + License version 2 as published by the Free Software Foundation. + </para> + + <para> + 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. + </para> + + <para> + 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 + </para> + + <para> + For more details see the file COPYING in the source + distribution of Linux. + </para> + + </legalnotice> + </bookinfo> + +<toc></toc> + + <chapter id="intro"> + <title>Introduction</title> + <para> + This documentation describes the driver for STMicroelectronics + STMPExxxx port expander devices. + </para> + </chapter> + + <chapter id="bugs"> + <title>Known Bugs And Assumptions</title> + <para> + <variablelist> + <varlistentry> + <term>None.</term> + <listitem> + <para> + None. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </chapter> + + <chapter id="pubfunctions"> + <title>Public Functions Provided</title> + <para> + List of public interfaces in stmpe driver + </para> +!Edrivers/mfd/stmpe.c + </chapter> + + <chapter id="private"> + <title>Private Functions</title> + <para> + STMPE Keypad driver + STMPE GPIO driver + </para> + <section id="stmpe-keypad.c"> + <title>stmpe-keypad.c</title> +!Idrivers/input/keyboard/stmpe-keypad.c + </section> + </chapter> + + <chapter id="Other"> + <title>Other Data Structures</title> + <para> + This Section lists some of the Data structure used by the stmpe driver and client drivers. + </para> +!Iinclude/linux/mfd/stmpe.h +!Idrivers/mfd/stmpe.h +</chapter> +</book> diff --git a/Documentation/DocBook/stylesheet.xsl b/Documentation/DocBook/stylesheet.xsl index 85b25275196..b2769ce5c8f 100644..100755 --- a/Documentation/DocBook/stylesheet.xsl +++ b/Documentation/DocBook/stylesheet.xsl @@ -1,10 +1,18 @@ -<?xml version="1.0" encoding="UTF-8"?> -<stylesheet xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0"> -<param name="chunk.quietly">1</param> -<param name="funcsynopsis.style">ansi</param> -<param name="funcsynopsis.tabular.threshold">80</param> -<param name="callout.graphics">0</param> -<!-- <param name="paper.type">A4</param> --> -<param name="generate.section.toc.level">2</param> -<param name="use.id.as.filename">1</param> -</stylesheet> +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:fo="http://www.w3.org/1999/XSL/Format" + version="1.0"> + <xsl:param name="use.id.as.filename" select="'1'"/> + <xsl:param name="admon.graphics" select="'1'"/> + <xsl:param name="admon.graphics.path"></xsl:param> + <xsl:param name="chunk.section.depth" select="2"></xsl:param> + <xsl:param name="chunk.quietly">1</xsl:param> + <xsl:param name="html.stylesheet" + select="'style.css'"/> + <xsl:param name="section.autolabel" select="1"/> + <xsl:param name="table.section.depth" select="1"/> + <xsl:param name="toc.section.depth" select="5"/> + <xsl:template name="user.header.content"> + <link href="../style.css" title="walsh" rel="stylesheet" type="text/css"/> + </xsl:template> +</xsl:stylesheet>
\ No newline at end of file diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index 54078ed96b3..af76fdef604 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -223,6 +223,7 @@ Code Seq#(hex) Include File Comments 'j' 00-3F linux/joystick.h 'k' 00-0F linux/spi/spidev.h conflict! 'k' 00-05 video/kyro.h conflict! +'k' 10-17 linux/hsi/hsi_char.h HSI character device 'l' 00-3F linux/tcfs_fs.h transparent cryptographic file system <http://web.archive.org/web/*/http://mikonos.dia.unisa.it/tcfs> 'l' 40-7F linux/udf_fs_i.h in development: @@ -443,7 +443,7 @@ asm-generic: no-dot-config-targets := clean mrproper distclean \ cscope gtags TAGS tags help %docs check% coccicheck \ include/linux/version.h headers_% \ - kernelversion %src-pkg + kernelrelease kernelversion %src-pkg config-targets := 0 mixed-targets := 0 @@ -947,7 +947,7 @@ $(vmlinux-dirs): prepare scripts # Store (new) KERNELRELASE string in include/config/kernel.release include/config/kernel.release: include/config/auto.conf FORCE $(Q)rm -f $@ - $(Q)echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))" > $@ + $(Q)echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion -s $(srctree) -t v$(KERNELVERSION))" > $@ # Things we need to do before we recursively start building the kernel @@ -1462,7 +1462,7 @@ checkstack: $(PERL) $(src)/scripts/checkstack.pl $(CHECKSTACK_ARCH) kernelrelease: - @echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))" + @echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion -s $(srctree) -t v$(KERNELVERSION))" kernelversion: @echo $(KERNELVERSION) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 3146ed3f6ec..0ec3f620d71 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -71,6 +71,10 @@ config KTIME_SCALAR bool default y +config KTIME_SCALAR + bool + default y + config HAVE_TCM bool select GENERIC_ALLOCATOR @@ -845,7 +849,9 @@ config ARCH_U8500 select GENERIC_CLOCKEVENTS select CLKDEV_LOOKUP select ARCH_REQUIRE_GPIOLIB + select HAVE_CLK select ARCH_HAS_CPUFREQ + select NOMADIK_GPIO help Support for ST-Ericsson's Ux500 architecture @@ -1604,7 +1610,9 @@ source "mm/Kconfig" config FORCE_MAX_ZONEORDER int "Maximum zone order" if ARCH_SHMOBILE range 11 64 if ARCH_SHMOBILE + depends on SA1111 || UX500_SOC_DB8500 default "9" if SA1111 + default "12" if UX500_SOC_DB8500 default "11" help The kernel memory allocator divides physically contiguous memory @@ -1887,6 +1895,13 @@ config KEXEC initially work for you. It may help to enable device hotplugging support. +config CRASH_SWRESET + bool "Perform a software reset at a panic (EXPERIMENTAL)" + depends on EXPERIMENTAL + depends on KEXEC + help + If no crash kernel has been loaded, perform a SW reset as plan B. + config ATAGS_PROC bool "Export atags in procfs" depends on KEXEC diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 97d31a4663d..661e8e469be 100644..100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -1,121 +1,333 @@ CONFIG_EXPERIMENTAL=y +# CONFIG_LOCALVERSION_AUTO is not set # CONFIG_SWAP is not set CONFIG_SYSVIPC=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_LOG_BUF_SHIFT=18 +CONFIG_CGROUPS=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_RELAY=y CONFIG_BLK_DEV_INITRD=y CONFIG_KALLSYMS_ALL=y +CONFIG_EMBEDDED=y +CONFIG_PERF_EVENTS=y +CONFIG_SLAB=y +CONFIG_BOOTTIME=y CONFIG_MODULES=y CONFIG_MODULE_UNLOAD=y -# CONFIG_LBDAF is not set +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y # CONFIG_BLK_DEV_BSG is not set +CONFIG_DEFAULT_DEADLINE=y CONFIG_ARCH_U8500=y -CONFIG_UX500_SOC_DB5500=y CONFIG_UX500_SOC_DB8500=y -CONFIG_MACH_U8500=y +CONFIG_MACH_HREFV60=y CONFIG_MACH_SNOWBALL=y -CONFIG_MACH_U5500=y +CONFIG_DBX500_PRCMU_DEBUG=y +CONFIG_DB8500_MLOADER=y +CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y +CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y +CONFIG_DISPLAY_SONY_ACX424AKP_DSI_PRIMARY=y +CONFIG_DISPLAY_AV8100_TERTIARY=y +CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y +CONFIG_UX500_SUSPEND=y +CONFIG_UX500_SUSPEND_STANDBY=y +CONFIG_UX500_SUSPEND_MEM=y +CONFIG_UX500_SUSPEND_DBG=y +CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y CONFIG_NR_CPUS=2 CONFIG_PREEMPT=y CONFIG_AEABI=y -CONFIG_CMDLINE="root=/dev/ram0 console=ttyAMA2,115200n8" +CONFIG_HIGHMEM=y +CONFIG_CMDLINE="root=/dev/ram0 init=init rw console=ttyAMA2,115200n8 mem=256M initrd=0x800000,72M" +CONFIG_KEXEC=y +CONFIG_CRASH_SWRESET=y +CONFIG_CRASH_DUMP=y CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT_DETAILS=y CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_IDLE=y +CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 +CONFIG_UX500_CPUIDLE_DEBUG=y +CONFIG_FPE_NWFPE=y CONFIG_VFP=y CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_PM_RUNTIME=y CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y +CONFIG_NET_KEY=y CONFIG_INET=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y CONFIG_IP_PNP=y CONFIG_IP_PNP_DHCP=y +# CONFIG_INET_LRO is not set +CONFIG_IPV6=y +# CONFIG_INET6_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET6_XFRM_MODE_TUNNEL is not set +# CONFIG_INET6_XFRM_MODE_BEET is not set +# CONFIG_IPV6_SIT is not set CONFIG_NETFILTER=y +CONFIG_NETFILTER_NETLINK_QUEUE=y +CONFIG_NETFILTER_NETLINK_LOG=y +CONFIG_NF_CONNTRACK=y +CONFIG_NF_CT_NETLINK=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_ULOG=y +CONFIG_NF_NAT=y +CONFIG_IP_NF_TARGET_MASQUERADE=y CONFIG_PHONET=y -# CONFIG_WIRELESS is not set +CONFIG_NET_SCHED=y +CONFIG_BT_L2CAP=y +CONFIG_BT_SCO=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=y +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_CFG80211=y +CONFIG_NL80211_TESTMODE=y +CONFIG_CFG80211_REG_DEBUG=y +CONFIG_RFKILL=y +CONFIG_RFKILL_INPUT=y +CONFIG_NET_9P=y CONFIG_CAIF=y CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +# CONFIG_STANDALONE is not set +CONFIG_BLK_DEV_LOOP=y CONFIG_BLK_DEV_RAM=y -CONFIG_BLK_DEV_RAM_SIZE=65536 +CONFIG_BLK_DEV_RAM_SIZE=73728 CONFIG_MISC_DEVICES=y CONFIG_AB8500_PWM=y CONFIG_SENSORS_BH1780=y +CONFIG_STE_TRACE_MODEM=y +CONFIG_DISPDEV=y +CONFIG_U8500_SIM_DETECT=y +CONFIG_STM_TRACE=y +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_DM_UEVENT=y CONFIG_NETDEVICES=y +CONFIG_TUN=y CONFIG_SMSC_PHY=y CONFIG_NET_ETHERNET=y CONFIG_SMSC911X=y # CONFIG_NETDEV_1000 is not set # CONFIG_NETDEV_10000 is not set # CONFIG_WLAN is not set -# CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_CAIF_TTY=m +CONFIG_CAIF_HSI=m +CONFIG_PPP=y +CONFIG_PPP_ASYNC=y +CONFIG_PPP_MPPE=y +# CONFIG_INPUT_MOUSEDEV is not set CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set CONFIG_KEYBOARD_GPIO=y -CONFIG_KEYBOARD_NOMADIK=y +CONFIG_KEYBOARD_NOMADIK_SKE=y CONFIG_KEYBOARD_STMPE=y CONFIG_KEYBOARD_TC3589X=y # CONFIG_INPUT_MOUSE is not set CONFIG_INPUT_TOUCHSCREEN=y CONFIG_TOUCHSCREEN_BU21013=y +CONFIG_TOUCHSCREEN_CYTTSP_CORE=y +CONFIG_TOUCHSCREEN_CYTTSP_SPI=y CONFIG_INPUT_MISC=y +CONFIG_INPUT_AB8500_ACCDET=y CONFIG_INPUT_AB8500_PONKEY=y -# CONFIG_SERIO is not set +CONFIG_INPUT_UINPUT=y CONFIG_VT_HW_CONSOLE_BINDING=y # CONFIG_LEGACY_PTYS is not set +# CONFIG_DEVKMEM is not set CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL=y CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM_NOMADIK=y -CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y CONFIG_I2C_NOMADIK=y CONFIG_SPI=y +# CONFIG_STM_MSP_SPI is not set CONFIG_SPI_PL022=y -CONFIG_GPIO_STMPE=y +CONFIG_GPIO_SYSFS=y CONFIG_GPIO_TC3589X=y -# CONFIG_HWMON is not set +CONFIG_GPIO_AB8500=y +CONFIG_POWER_SUPPLY=y +CONFIG_AB8500_BM=y +CONFIG_SENSORS_AB8500=y +CONFIG_SENSORS_LSM303DLH=y +CONFIG_SENSORS_L3G4200D=y +CONFIG_WATCHDOG=y +CONFIG_U8500_WATCHDOG_DEBUG=y +CONFIG_TPS6105X=y CONFIG_MFD_STMPE=y CONFIG_MFD_TC3589X=y +CONFIG_AB5500_CORE=y CONFIG_AB8500_CORE=y +CONFIG_MFD_DB8500_PRCMU=y +CONFIG_REGULATOR_DEBUG=y +CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y -# CONFIG_HID_SUPPORT is not set +CONFIG_REGULATOR_DB8500_PRCMU=y +CONFIG_REGULATOR_AB8500_DEBUG=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +# CONFIG_VIDEO_CAPTURE_DRIVERS is not set +CONFIG_RADIO_CG2900=y +CONFIG_DRM=y +CONFIG_GPU_MALI=y +CONFIG_FB=y +CONFIG_FB_MCDE=y +CONFIG_MCDE_FB_AVOID_REALLOC=y +CONFIG_MCDE_DISPLAY_SAMSUNG_S6D16D0=y +# CONFIG_MCDE_DISPLAY_HDMI_FB_AUTO_CREATE is not set +CONFIG_AV8100_HWTRIG_I2SDAT3=y +CONFIG_FB_B2R2=y +CONFIG_B2R2_PLUG_CONF=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_USB_AUDIO=y +CONFIG_SND_SOC=y +CONFIG_SND_SOC_UX500=y +CONFIG_SND_SOC_UX500_AB5500=y +CONFIG_SND_SOC_UX500_AB8500=y +CONFIG_SND_SOC_UX500_CG29XX=y +CONFIG_SND_SOC_UX500_AV8100=y +CONFIG_USB=y +# CONFIG_USB_DEVICE_CLASS is not set +CONFIG_USB_SUSPEND=y +CONFIG_USB_OTG=y +# CONFIG_USB_OTG_WHITELIST is not set +CONFIG_USB_MON=y CONFIG_USB_MUSB_HDRC=y -CONFIG_USB_GADGET_MUSB_HDRC=y -CONFIG_MUSB_PIO_ONLY=y +CONFIG_USB_MUSB_UX500=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_LIBUSUAL=y CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_USB_GADGET_MUSB_HDRC=m +CONFIG_USB_ZERO=m +CONFIG_USB_ETH=m +CONFIG_USB_FILE_STORAGE=m +CONFIG_USB_MASS_STORAGE=m +CONFIG_USB_G_SERIAL=m +CONFIG_USB_CDC_COMPOSITE=m +CONFIG_USB_G_MULTI=m +# CONFIG_USB_G_MULTI_RNDIS is not set +CONFIG_USB_G_HID=m CONFIG_AB8500_USB=y CONFIG_MMC=y +CONFIG_MMC_UNSAFE_RESUME=y CONFIG_MMC_CLKGATE=y +# CONFIG_MMC_BLOCK_BOUNCE is not set CONFIG_MMC_ARMMMCI=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y CONFIG_LEDS_LM3530=y CONFIG_LEDS_LP5521=y +CONFIG_LEDS_PWM=y +CONFIG_LEDS_TRIGGERS=y +CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_AB=y CONFIG_RTC_DRV_AB8500=y -CONFIG_RTC_DRV_PL031=y CONFIG_DMADEVICES=y CONFIG_STE_DMA40=y CONFIG_STAGING=y +CONFIG_AB5500_SIM=y CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4=y +CONFIG_CG2900=y +CONFIG_CG2900_CHIP=y +CONFIG_STLC2690_CHIP=y +CONFIG_CG2900_UART=y +CONFIG_CG2900_AUDIO=y +CONFIG_CG2900_TEST=y +CONFIG_BT_CG2900=y +CONFIG_CW1200=m +CONFIG_CW1200_USE_GPIO_IRQ=y +CONFIG_CW1200_DEBUGFS=y +CONFIG_U8500_MMIO=y +CONFIG_U8500_CM=y +CONFIG_U8500_FLASH=y +CONFIG_MODEM=y +CONFIG_MODEM_U8500=y +CONFIG_U8500_SHRM=y +CONFIG_U8500_SHRM_MODEM_SILENT_RESET=y CONFIG_EXT2_FS=y CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y CONFIG_EXT2_FS_SECURITY=y CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT3_FS_POSIX_ACL=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_QUOTA=y +CONFIG_QFMT_V2=y +CONFIG_AUTOFS4_FS=m +CONFIG_ISO9660_FS=y +CONFIG_JOLIET=y +CONFIG_ZISOFS=y +CONFIG_UDF_FS=m +CONFIG_MSDOS_FS=y CONFIG_VFAT_FS=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y -CONFIG_CONFIGFS_FS=m -# CONFIG_MISC_FILESYSTEMS is not set -CONFIG_NFS_FS=y -CONFIG_ROOT_NFS=y +CONFIG_HFS_FS=m +CONFIG_BEFS_FS=m +CONFIG_CRAMFS=m +CONFIG_VXFS_FS=m +CONFIG_MINIX_FS=m +CONFIG_ROMFS_FS=m +CONFIG_SYSV_FS=m +CONFIG_UFS_FS=m +CONFIG_9P_FS=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_BLKDEV_PARTITION=y +CONFIG_BSD_DISKLABEL=y +CONFIG_MINIX_SUBPARTITION=y +CONFIG_SOLARIS_X86_PARTITION=y +CONFIG_UNIXWARE_DISKLABEL=y +CONFIG_SGI_PARTITION=y +CONFIG_SUN_PARTITION=y CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ISO8859_1=y +CONFIG_PRINTK_TIME=y CONFIG_MAGIC_SYSRQ=y -CONFIG_DEBUG_FS=y -CONFIG_DEBUG_KERNEL=y -# CONFIG_SCHED_DEBUG is not set +CONFIG_UNUSED_SYMBOLS=y +CONFIG_DETECT_HUNG_TASK=y +CONFIG_SCHEDSTATS=y +CONFIG_TIMER_STATS=y # CONFIG_DEBUG_PREEMPT is not set CONFIG_DEBUG_INFO=y -# CONFIG_FTRACE is not set +CONFIG_SYSCTL_SYSCALL_CHECK=y +CONFIG_FUNCTION_TRACER=y +CONFIG_DYNAMIC_DEBUG=y CONFIG_DEBUG_USER=y +CONFIG_KEYS=y +CONFIG_CRYPTO_MD5=m +CONFIG_CRYPTO_TWOFISH=y +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_DEV_UX500=y +CONFIG_CRYPTO_DEV_UX500_HASH=y +CONFIG_CRC7=y +CONFIG_LIBCRC32C=m diff --git a/arch/arm/include/asm/cacheflush.h b/arch/arm/include/asm/cacheflush.h index d5d8d5c7268..a4bf3199819 100644 --- a/arch/arm/include/asm/cacheflush.h +++ b/arch/arm/include/asm/cacheflush.h @@ -84,6 +84,14 @@ * - kaddr - page address * - size - region size * + * clean_dcache_all() + * + * Cleans the entire d-cache. + * + * flush_dcache_all() + * + * Flushes the entire d-cache. + * * DMA Cache Coherency * =================== * @@ -104,6 +112,9 @@ struct cpu_cache_fns { void (*coherent_user_range)(unsigned long, unsigned long); void (*flush_kern_dcache_area)(void *, size_t); + void (*clean_dcache_all)(void); + void (*flush_dcache_all)(void); + void (*dma_map_area)(const void *, size_t, int); void (*dma_unmap_area)(const void *, size_t, int); @@ -124,6 +135,8 @@ extern struct cpu_cache_fns cpu_cache; #define __cpuc_coherent_kern_range cpu_cache.coherent_kern_range #define __cpuc_coherent_user_range cpu_cache.coherent_user_range #define __cpuc_flush_dcache_area cpu_cache.flush_kern_dcache_area +#define __cpuc_clean_dcache_all cpu_cache.clean_dcache_all +#define __cpuc_flush_dcache_all cpu_cache.flush_dcache_all /* * These are private to the dma-mapping API. Do not use directly. @@ -144,6 +157,8 @@ extern void __cpuc_flush_user_range(unsigned long, unsigned long, unsigned int); extern void __cpuc_coherent_kern_range(unsigned long, unsigned long); extern void __cpuc_coherent_user_range(unsigned long, unsigned long); extern void __cpuc_flush_dcache_area(void *, size_t); +extern void __cpuc_clean_dcache_all(void); +extern void __cpuc_flush_dcache_all(void); /* * These are private to the dma-mapping API. Do not use directly. diff --git a/arch/arm/include/asm/delay.h b/arch/arm/include/asm/delay.h index b2deda18154..91063a3976f 100644 --- a/arch/arm/include/asm/delay.h +++ b/arch/arm/include/asm/delay.h @@ -8,7 +8,7 @@ #include <asm/param.h> /* HZ */ -extern void __delay(int loops); +extern void __delay(unsigned long loops); /* * This function intentionally does not exist; if you see references to @@ -40,5 +40,14 @@ extern void __const_udelay(unsigned long); __const_udelay((n) * ((2199023U*HZ)>>11))) : \ __udelay(n)) +extern void (*delay_fn)(unsigned long); + +static inline void set_delay_fn(void (*fn)(unsigned long)) +{ + delay_fn = fn; +} + +extern void read_current_timer_delay_loop(unsigned long loops); + #endif /* defined(_ARM_DELAY_H) */ diff --git a/arch/arm/include/asm/elf.h b/arch/arm/include/asm/elf.h index 0e9ce8d9686..85cf3655914 100644 --- a/arch/arm/include/asm/elf.h +++ b/arch/arm/include/asm/elf.h @@ -96,8 +96,8 @@ struct elf32_hdr; /* * This is used to ensure we don't load something for the wrong architecture. */ -extern int elf_check_arch(const struct elf32_hdr *); -#define elf_check_arch elf_check_arch +extern int arm_elf_check_arch(const struct elf32_hdr *); +#define elf_check_arch(x) arm_elf_check_arch((const struct elf32_hdr *)(x)) #define vmcore_elf64_check_arch(x) (0) diff --git a/arch/arm/include/asm/io.h b/arch/arm/include/asm/io.h index d66605dea55..ffa4430e106 100644 --- a/arch/arm/include/asm/io.h +++ b/arch/arm/include/asm/io.h @@ -63,6 +63,12 @@ extern void __raw_readsl(const void __iomem *addr, void *data, int longlen); #define MT_DEVICE_CACHED 2 #define MT_DEVICE_WC 3 /* + * NOTE : U8500 v1.0/ED cut specific hack. + * look at the commit message for more details + */ +#define MT_BACKUP_RAM 4 + +/* * types 4 onwards can be found in asm/mach/map.h and are undefined * for ioremap */ diff --git a/arch/arm/include/asm/outercache.h b/arch/arm/include/asm/outercache.h index d8387437ec5..7ed8f98b217 100644 --- a/arch/arm/include/asm/outercache.h +++ b/arch/arm/include/asm/outercache.h @@ -33,6 +33,8 @@ struct outer_cache_fns { #ifdef CONFIG_OUTER_CACHE_SYNC void (*sync)(void); #endif + void (*prefetch_enable)(void); + void (*prefetch_disable)(void); void (*set_debug)(unsigned long); }; @@ -74,6 +76,18 @@ static inline void outer_disable(void) outer_cache.disable(); } +static inline void outer_prefetch_enable(void) +{ + if (outer_cache.prefetch_enable) + outer_cache.prefetch_enable(); +} + +static inline void outer_prefetch_disable(void) +{ + if (outer_cache.prefetch_disable) + outer_cache.prefetch_disable(); +} + #else static inline void outer_inv_range(phys_addr_t start, phys_addr_t end) diff --git a/arch/arm/include/asm/smp_twd.h b/arch/arm/include/asm/smp_twd.h index fed9981fba0..2379b5f930a 100644 --- a/arch/arm/include/asm/smp_twd.h +++ b/arch/arm/include/asm/smp_twd.h @@ -25,4 +25,12 @@ extern void __iomem *twd_base; int twd_timer_ack(void); void twd_timer_setup(struct clock_event_device *); +#if defined(CONFIG_HOTPLUG) || defined(CONFIG_CPU_IDLE) +void twd_save(void); +void twd_restore(void); +#else +static inline void twd_save(void) { } +static inline void twd_restore(void) { } +#endif + #endif diff --git a/arch/arm/include/asm/system.h b/arch/arm/include/asm/system.h index 832888d0c20..42d7d5c6594 100644 --- a/arch/arm/include/asm/system.h +++ b/arch/arm/include/asm/system.h @@ -110,6 +110,8 @@ extern void cpu_init(void); void arm_machine_restart(char mode, const char *cmd); extern void (*arm_pm_restart)(char str, const char *cmd); +void cpu_idle_wait(void); + #define UDBG_UNDEFINED (1 << 0) #define UDBG_SYSCALL (1 << 1) #define UDBG_BADABORT (1 << 2) diff --git a/arch/arm/kernel/armksyms.c b/arch/arm/kernel/armksyms.c index aeef960ff79..15f151e02d6 100644 --- a/arch/arm/kernel/armksyms.c +++ b/arch/arm/kernel/armksyms.c @@ -52,10 +52,6 @@ extern void fpundefinstr(void); EXPORT_SYMBOL(__backtrace); - /* platform dependent support */ -EXPORT_SYMBOL(__udelay); -EXPORT_SYMBOL(__const_udelay); - /* networking */ EXPORT_SYMBOL(csum_partial); EXPORT_SYMBOL(csum_partial_copy_from_user); diff --git a/arch/arm/kernel/elf.c b/arch/arm/kernel/elf.c index 9b05c6a0dce..ddd49b23365 100644 --- a/arch/arm/kernel/elf.c +++ b/arch/arm/kernel/elf.c @@ -4,11 +4,13 @@ #include <linux/binfmts.h> #include <linux/elf.h> -int elf_check_arch(const struct elf32_hdr *x) +int arm_elf_check_arch(const struct elf32_hdr *x) { unsigned int eflags; /* Make sure it's an ARM executable */ + if (x->e_ident[EI_CLASS] != ELF_CLASS) + return 0; if (x->e_machine != EM_ARM) return 0; @@ -35,7 +37,7 @@ int elf_check_arch(const struct elf32_hdr *x) } return 1; } -EXPORT_SYMBOL(elf_check_arch); +EXPORT_SYMBOL(arm_elf_check_arch); void elf_set_personality(const struct elf32_hdr *x) { diff --git a/arch/arm/kernel/machine_kexec.c b/arch/arm/kernel/machine_kexec.c index e59bbd496c3..2a9c3430115 100644 --- a/arch/arm/kernel/machine_kexec.c +++ b/arch/arm/kernel/machine_kexec.c @@ -47,6 +47,7 @@ void machine_crash_nonpanic_core(void *unused) printk(KERN_DEBUG "CPU %u will stop doing anything useful since another CPU has crashed\n", smp_processor_id()); crash_save_cpu(®s, smp_processor_id()); + atomic_notifier_call_chain(&crash_percpu_notifier_list, 0, NULL); flush_cache_all(); atomic_dec(&waiting_for_crash_ipi); @@ -122,3 +123,13 @@ void machine_kexec(struct kimage *image) flush_cache_all(); cpu_reset(reboot_code_buffer_phys); } + +void machine_crash_swreset(void) +{ + printk(KERN_INFO "Software reset on panic!\n"); + + flush_cache_all(); + outer_flush_all(); + outer_disable(); + arm_pm_restart(0, NULL); +} diff --git a/arch/arm/kernel/process.c b/arch/arm/kernel/process.c index 1a347f481e5..f48b1c2e958 100644 --- a/arch/arm/kernel/process.c +++ b/arch/arm/kernel/process.c @@ -187,8 +187,17 @@ void cpu_idle(void) leds_event(led_idle_start); while (!need_resched()) { #ifdef CONFIG_HOTPLUG_CPU - if (cpu_is_offline(smp_processor_id())) + if (cpu_is_offline(smp_processor_id())) { + + /* NOTE : preempt_count() should be 0 for dying CPU + * as the CPU will use this very thread when + * it is alive + */ + if (preempt_count()) + preempt_enable_no_resched(); + cpu_die(); + } #endif local_irq_disable(); diff --git a/arch/arm/kernel/return_address.c b/arch/arm/kernel/return_address.c index 0b13a72f855..b7fe66d270a 100644 --- a/arch/arm/kernel/return_address.c +++ b/arch/arm/kernel/return_address.c @@ -58,10 +58,6 @@ void *return_address(unsigned int level) #else /* if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND) */ -#if defined(CONFIG_ARM_UNWIND) -#warning "TODO: return_address should use unwind tables" -#endif - void *return_address(unsigned int level) { return NULL; diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index d88ff0230e8..fe9e2954ee4 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -313,8 +313,6 @@ asmlinkage void __cpuinit secondary_start_kernel(void) */ percpu_timer_setup(); - calibrate_delay(); - smp_store_cpu_info(cpu); /* @@ -485,7 +483,7 @@ void show_local_irqs(struct seq_file *p, int prec) #endif #ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST -static void smp_timer_broadcast(const struct cpumask *mask) +void smp_timer_broadcast(const struct cpumask *mask) { smp_cross_call(mask, IPI_TIMER); } diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 01c186222f3..36c68def2a4 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -10,13 +10,17 @@ */ #include <linux/init.h> #include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/err.h> #include <linux/smp.h> #include <linux/jiffies.h> #include <linux/clockchips.h> #include <linux/irq.h> #include <linux/io.h> +#include <linux/percpu.h> #include <asm/smp_twd.h> #include <asm/hardware/gic.h> @@ -24,7 +28,12 @@ /* set up by the platform code */ void __iomem *twd_base; +static struct clk *twd_clk; static unsigned long twd_timer_rate; +static DEFINE_PER_CPU(struct clock_event_device *, twd_ce); + +static DEFINE_PER_CPU(u32, twd_ctrl); +static DEFINE_PER_CPU(u32, twd_load); static void twd_set_mode(enum clock_event_mode mode, struct clock_event_device *clk) @@ -80,6 +89,52 @@ int twd_timer_ack(void) return 0; } +#ifdef CONFIG_CPU_FREQ + +/* + * Updates clockevent frequency when the cpu frequency changes. + * Called on the cpu that is changing frequency with interrupts disabled. + */ +static void twd_update_frequency(void *data) +{ + twd_timer_rate = clk_get_rate(twd_clk); + + clockevents_update_freq(__get_cpu_var(twd_ce), twd_timer_rate); +} + +static int twd_cpufreq_transition(struct notifier_block *nb, + unsigned long state, void *data) +{ + struct cpufreq_freqs *freqs = data; + + /* + * The twd clock events must be reprogrammed to account for the new + * frequency. The timer is local to a cpu, so cross-call to the + * changing cpu. + */ + if (state == CPUFREQ_POSTCHANGE || state == CPUFREQ_RESUMECHANGE) + smp_call_function_single(freqs->cpu, twd_update_frequency, + NULL, 1); + + return NOTIFY_OK; +} + +static struct notifier_block twd_cpufreq_nb = { + .notifier_call = twd_cpufreq_transition, +}; + +static int twd_cpufreq_init(void) +{ + if (!IS_ERR_OR_NULL(twd_clk)) + return cpufreq_register_notifier(&twd_cpufreq_nb, + CPUFREQ_TRANSITION_NOTIFIER); + + return 0; +} +core_initcall(twd_cpufreq_init); + +#endif + static void __cpuinit twd_calibrate_rate(void) { unsigned long count; @@ -119,12 +174,39 @@ static void __cpuinit twd_calibrate_rate(void) } } +static struct clk *twd_get_clock(void) +{ + struct clk *clk; + int err; + + clk = clk_get_sys("smp_twd", NULL); + if (IS_ERR(clk)) { + pr_err("smp_twd: clock not found: %d\n", (int)PTR_ERR(clk)); + return clk; + } + + err = clk_enable(clk); + if (err) { + pr_err("smp_twd: clock failed to enable: %d\n", err); + clk_put(clk); + return ERR_PTR(err); + } + + return clk; +} + /* * Setup the local clock events for a CPU. */ void __cpuinit twd_timer_setup(struct clock_event_device *clk) { - twd_calibrate_rate(); + if (!twd_clk) + twd_clk = twd_get_clock(); + + if (!IS_ERR_OR_NULL(twd_clk)) + twd_timer_rate = clk_get_rate(twd_clk); + else + twd_calibrate_rate(); clk->name = "local_timer"; clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT | @@ -132,13 +214,33 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) clk->rating = 350; clk->set_mode = twd_set_mode; clk->set_next_event = twd_set_next_event; - clk->shift = 20; - clk->mult = div_sc(twd_timer_rate, NSEC_PER_SEC, clk->shift); - clk->max_delta_ns = clockevent_delta2ns(0xffffffff, clk); - clk->min_delta_ns = clockevent_delta2ns(0xf, clk); - clockevents_register_device(clk); + __get_cpu_var(twd_ce) = clk; + + clockevents_config_and_register(clk, twd_timer_rate, + 0xf, 0xffffffff); /* Make sure our local interrupt controller has this enabled */ gic_enable_ppi(clk->irq); } + +#if defined(CONFIG_HOTPLUG) || defined(CONFIG_CPU_IDLE) +void twd_save(void) +{ + int this_cpu = smp_processor_id(); + + per_cpu(twd_ctrl, this_cpu) = __raw_readl(twd_base + TWD_TIMER_CONTROL); + per_cpu(twd_load, this_cpu) = __raw_readl(twd_base + TWD_TIMER_LOAD); + +} + +void twd_restore(void) +{ + int this_cpu = smp_processor_id(); + + __raw_writel(per_cpu(twd_ctrl, this_cpu), + twd_base + TWD_TIMER_CONTROL); + __raw_writel(per_cpu(twd_load, this_cpu), + twd_base + TWD_TIMER_LOAD); +} +#endif diff --git a/arch/arm/lib/delay.S b/arch/arm/lib/delay.S deleted file mode 100644 index 3c9a05c8d20..00000000000 --- a/arch/arm/lib/delay.S +++ /dev/null @@ -1,69 +0,0 @@ -/* - * linux/arch/arm/lib/delay.S - * - * Copyright (C) 1995, 1996 Russell King - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ -#include <linux/linkage.h> -#include <asm/assembler.h> -#include <asm/param.h> - .text - -.LC0: .word loops_per_jiffy -.LC1: .word (2199023*HZ)>>11 - -/* - * r0 <= 2000 - * lpj <= 0x01ffffff (max. 3355 bogomips) - * HZ <= 1000 - */ - -ENTRY(__udelay) - ldr r2, .LC1 - mul r0, r2, r0 -ENTRY(__const_udelay) @ 0 <= r0 <= 0x7fffff06 - mov r1, #-1 - ldr r2, .LC0 - ldr r2, [r2] @ max = 0x01ffffff - add r0, r0, r1, lsr #32-14 - mov r0, r0, lsr #14 @ max = 0x0001ffff - add r2, r2, r1, lsr #32-10 - mov r2, r2, lsr #10 @ max = 0x00007fff - mul r0, r2, r0 @ max = 2^32-1 - add r0, r0, r1, lsr #32-6 - movs r0, r0, lsr #6 - moveq pc, lr - -/* - * loops = r0 * HZ * loops_per_jiffy / 1000000 - * - * Oh, if only we had a cycle counter... - */ - -@ Delay routine -ENTRY(__delay) - subs r0, r0, #1 -#if 0 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 -#endif - bhi __delay - mov pc, lr -ENDPROC(__udelay) -ENDPROC(__const_udelay) -ENDPROC(__delay) diff --git a/arch/arm/lib/delay.c b/arch/arm/lib/delay.c new file mode 100644 index 00000000000..b8d636e8ef8 --- /dev/null +++ b/arch/arm/lib/delay.c @@ -0,0 +1,81 @@ +/* + * Originally from linux/arch/arm/lib/delay.S + * + * Copyright (C) 1995, 1996 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/timex.h> + +/* + * Oh, if only we had a cycle counter... + */ +static void delay_loop(unsigned long loops) +{ + asm volatile( + "1: subs %0, %0, #1 \n" + " bhi 1b \n" + : /* No output */ + : "r" (loops) + ); +} + +#ifdef ARCH_HAS_READ_CURRENT_TIMER +/* + * Assumes read_current_timer() is monotonically increasing + * across calls and wraps at most once within MAX_UDELAY_MS. + */ +void read_current_timer_delay_loop(unsigned long loops) +{ + unsigned long bclock, now; + + read_current_timer(&bclock); + do { + read_current_timer(&now); + } while ((now - bclock) < loops); +} +#endif + +void (*delay_fn)(unsigned long) = delay_loop; + +/* + * loops = usecs * HZ * loops_per_jiffy / 1000000 + */ +void __delay(unsigned long loops) +{ + delay_fn(loops); +} +EXPORT_SYMBOL(__delay); + +/* + * 0 <= xloops <= 0x7fffff06 + * loops_per_jiffy <= 0x01ffffff (max. 3355 bogomips) + */ +void __const_udelay(unsigned long xloops) +{ + unsigned long lpj; + unsigned long loops; + + xloops >>= 14; /* max = 0x01ffffff */ + lpj = loops_per_jiffy >> 10; /* max = 0x0001ffff */ + loops = lpj * xloops; /* max = 0x00007fff */ + loops >>= 6; /* max = 2^32-1 */ + + if (likely(loops)) + __delay(loops); +} +EXPORT_SYMBOL(__const_udelay); + +/* + * usecs <= 2000 + * HZ <= 1000 + */ +void __udelay(unsigned long usecs) +{ + __const_udelay(usecs * ((2199023UL*HZ)>>11)); +} +EXPORT_SYMBOL(__udelay); diff --git a/arch/arm/mach-ux500/Kconfig b/arch/arm/mach-ux500/Kconfig index a3e0c8692f0..a3a0dd9f60f 100644 --- a/arch/arm/mach-ux500/Kconfig +++ b/arch/arm/mach-ux500/Kconfig @@ -4,20 +4,25 @@ config UX500_SOC_COMMON bool default y select ARM_GIC - select HAS_MTU + select NOMADIK_GPIO select ARM_ERRATA_753970 select ARM_ERRATA_754322 + select SYS_SOC + select HAS_MTU + +config UX500_SOC_DBX500 + depends on UX500_SOC_DB5500 || UX500_SOC_DB8500 + bool menu "Ux500 SoC" config UX500_SOC_DB5500 bool "DB5500" - select MFD_DB5500_PRCMU + select UX500_SOC_DBX500 config UX500_SOC_DB8500 bool "DB8500" - select MFD_DB8500_PRCMU - select REGULATOR_DB8500_PRCMU + select UX500_SOC_DBX500 endmenu @@ -26,13 +31,13 @@ menu "Ux500 target platform (boards)" config MACH_U8500 bool "U8500 Development platform" depends on UX500_SOC_DB8500 - select TPS6105X help Include support for the mop500 development platform. config MACH_HREFV60 bool "U85000 Development platform, HREFv60 version" depends on UX500_SOC_DB8500 + select MACH_U8500 help Include support for the HREFv60 new development platform. @@ -50,6 +55,24 @@ config MACH_U5500 Include support for the U5500 development platform. endmenu +choice + prompt "Ux500 UIB Keylayout" + default KEYLAYOUT_LAYOUT1 + +config KEYLAYOUT_LAYOUT1 + bool "UIB Keylayout 1; for generic users" + help + Supported keylayout for some numerics, power/call buttons, + volume control etc + +config KEYLAYOUT_LAYOUT2 + bool "UIB Keylayout 2; for connectivity users" + help + Supports keylayout numerics 0-9, left/right/up/down/back/ + enter keys and special character "."(dot) + +endchoice + config UX500_DEBUG_UART int "Ux500 UART to use for low-level debug" default 2 @@ -57,6 +80,13 @@ config UX500_DEBUG_UART Choose the UART on which kernel low-level debug messages should be output. +config SENSORS1P_MOP + tristate "HAL and Proximity sensors support" + depends on REGULATOR && (GPIO_STMPE2401 || GPIO_TC35892) + help + Add support for Osram's SFH7741 Proximity Sensor and Samsumg + HED54XXU11 HAL Switch + config U5500_MODEM_IRQ bool "Modem IRQ support" depends on UX500_SOC_DB5500 @@ -64,11 +94,48 @@ config U5500_MODEM_IRQ help Add support for handling IRQ:s from modem side -config U5500_MBOX - bool "Mailbox support" - depends on U5500_MODEM_IRQ +config DBX500_PRCMU_DEBUG + bool "DBX500 PRCMU debug" + depends on ((MFD_DB5500_PRCMU || MFD_DB8500_PRCMU) && DEBUG_FS) + help + Add support for PRCMU debug + +config TEE_UX500 + bool "Trusted Execution Environment (TEE) ux500 hardware support" + depends on TEE_SUPPORT default y help - Add support for U5500 mailbox communication with modem side + Adds TEE hardware support for ux500 platforms. + +config TEE_SVP + bool "Trusted Execution Environment (TEE) ux500 SVP support" + depends on TEE_SUPPORT && UX500_SVP + default y + help + Adds TEE support for SVP in ux500 platforms. + +config UX500_DEBUG_HWREG + bool "Debug hardware registers from userspace" + depends on (DEBUG_FS && UX500_SOC_DB8500) + help + Adds various debug files to access registers. + This should never ever be used for anything else than debugging. + +config UX500_DEBUG_NO_LAUTERBACH + bool "Disable clocks needed for Lauterbach debugging" + help + Disable clocks needed for Lauterbach debugging at boot. + If yes, you will reduce the power consumption. + +config UX500_L2X0_PREFETCH_CTRL + bool "PL310 prefetch control" + depends on (UX500_SOC_DB8500 || UX500_SOC_DB5500) && \ + (TEE_UX500 && CACHE_L2X0) + default y + help + Adds interface to control instruction and data prefetch. + Communication with Trustzone is done through TEE driver. + +source "arch/arm/mach-ux500/pm/Kconfig" endif diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile index 1694916e682..128377f4078 100644 --- a/arch/arm/mach-ux500/Makefile +++ b/arch/arm/mach-ux500/Makefile @@ -1,20 +1,70 @@ # -# Makefile for the linux kernel, U8500 machine. +# Makefile for the linux kernel, UX500 machine. # -obj-y := clock.o cpu.o devices.o devices-common.o \ - id.o usb.o -obj-$(CONFIG_UX500_SOC_DB5500) += cpu-db5500.o dma-db5500.o -obj-$(CONFIG_UX500_SOC_DB8500) += cpu-db8500.o devices-db8500.o +obj-y := clock.o cpu.o devices.o \ + devices-common.o id.o pins.o \ + usb.o reboot_reasons.o timer.o \ + uart-db8500.o clock-debug.o +obj-y += pm/ + + + +ifeq ($(CONFIG_UX500_SOC_DB5500), y) +obj-$(CONFIG_UX500_SOC_DBX500) += cpu-db5500.o dma-db5500.o \ + devices-db5500.o clock-db5500.o +endif +ifeq ($(CONFIG_UX500_SOC_DB8500), y) +obj-$(CONFIG_UX500_SOC_DBX500) += cpu-db8500.o devices-db8500.o \ + clock-db8500.o dma-db8500.o +endif obj-$(CONFIG_MACH_U8500) += board-mop500.o board-mop500-sdi.o \ board-mop500-regulators.o \ board-mop500-uib.o board-mop500-stuib.o \ - board-mop500-u8500uib.o \ - board-mop500-pins.o -obj-$(CONFIG_MACH_U5500) += board-u5500.o board-u5500-sdi.o + board-mop500-u8500uib.o board-mop500-pins.o \ + board-mop500-bm.o \ + board-pins-sleep-force.o +obj-$(CONFIG_SENSORS1P_MOP) += sensors1p.o +obj-$(CONFIG_MACH_U5500) += board-u5500.o board-u5500-sdi.o \ + board-u5500-regulators.o \ + board-u5500-pins.o +obj-$(CONFIG_U5500_MMIO) += board-u5500-mmio.o +obj-$(CONFIG_U8500_MMIO) += board-mop500-mmio.o obj-$(CONFIG_SMP) += platsmp.o headsmp.o obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o obj-$(CONFIG_U5500_MODEM_IRQ) += modem-irq-db5500.o -obj-$(CONFIG_U5500_MBOX) += mbox-db5500.o +obj-$(CONFIG_TEE_UX500) += tee_ux500.o product.o +obj-$(CONFIG_TEE_SVP) += tee_service_svp.o +obj-$(CONFIG_TEE_SVP) += tee_ta_start_modem_svp.o +obj-$(CONFIG_DB8500_MLOADER) += mloader-db8500.o +obj-$(CONFIG_U5500_MLOADER) += mloader-db5500.o +obj-$(CONFIG_UX500_DEBUG_HWREG) += hwreg.o +obj-$(CONFIG_HWMEM) += hwmem-int.o +obj-$(CONFIG_UX500_L2X0_PREFETCH_CTRL) += l2x0-prefetch.o +obj-$(CONFIG_AB5500_BM) += board-u5500-bm.o +obj-$(CONFIG_DBX500_PRCMU_DEBUG) += prcmu-debug.o + +obj-$(CONFIG_HWMEM) += dcache.o +ifdef CONFIG_STM_TRACE +obj-$(CONFIG_MACH_U8500) += board-mop500-stm.o +endif +ifdef CONFIG_SENSORS_LSM303DLH +obj-$(CONFIG_MACH_U8500) += board-mop500-sensors.o +endif +ifdef CONFIG_FB_MCDE +obj-$(CONFIG_MACH_U8500) += board-mop500-mcde.o +obj-$(CONFIG_MACH_U5500) += board-u5500-mcde.o +endif +ifdef CONFIG_STM_MSP_I2S +obj-$(CONFIG_MACH_U8500) += board-mop500-msp.o +endif +ifdef CONFIG_CW1200 +obj-$(CONFIG_MACH_U8500) += board-mop500-wlan.o +obj-$(CONFIG_MACH_U5500) += board-u5500-wlan.o +endif +ifdef CONFIG_TOUCHSCREEN_CYTTSP_SPI +obj-$(CONFIG_MACH_U8500) += board-mop500-cyttsp.o +obj-$(CONFIG_MACH_U5500) += board-u5500-cyttsp.o +endif diff --git a/arch/arm/mach-ux500/board-mop500-bm.c b/arch/arm/mach-ux500/board-mop500-bm.c new file mode 100644 index 00000000000..91fb887d4f5 --- /dev/null +++ b/arch/arm/mach-ux500/board-mop500-bm.c @@ -0,0 +1,422 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U8500 board specific charger and battery initialization parameters. + * + * Author: Johan Palsson <johan.palsson@stericsson.com> for ST-Ericsson. + * Author: Johan Gardsmark <johan.gardsmark@stericsson.com> for ST-Ericsson. + * + */ + +#include <linux/power_supply.h> +#include <linux/mfd/ab8500/bm.h> +#include "board-mop500-bm.h" + +#ifdef CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL +/* + * These are the defined batteries that uses a NTC and ID resistor placed + * inside of the battery pack. + * Note that the res_to_temp table must be strictly sorted by falling resistance + * values to work. + */ +static struct res_to_temp temp_tbl_A[] = { + {-5, 53407}, + { 0, 48594}, + { 5, 43804}, + {10, 39188}, + {15, 34870}, + {20, 30933}, + {25, 27422}, + {30, 24347}, + {35, 21694}, + {40, 19431}, + {45, 17517}, + {50, 15908}, + {55, 14561}, + {60, 13437}, + {65, 12500}, +}; +static struct res_to_temp temp_tbl_B[] = { + {-5, 165418}, + { 0, 159024}, + { 5, 151921}, + {10, 144300}, + {15, 136424}, + {20, 128565}, + {25, 120978}, + {30, 113875}, + {35, 107397}, + {40, 101629}, + {45, 96592}, + {50, 92253}, + {55, 88569}, + {60, 85461}, + {65, 82869}, +}; +static struct v_to_cap cap_tbl_A[] = { + {4171, 100}, + {4114, 95}, + {4009, 83}, + {3947, 74}, + {3907, 67}, + {3863, 59}, + {3830, 56}, + {3813, 53}, + {3791, 46}, + {3771, 33}, + {3754, 25}, + {3735, 20}, + {3717, 17}, + {3681, 13}, + {3664, 8}, + {3651, 6}, + {3635, 5}, + {3560, 3}, + {3408, 1}, + {3247, 0}, +}; +static struct v_to_cap cap_tbl_B[] = { + {4161, 100}, + {4124, 98}, + {4044, 90}, + {4003, 85}, + {3966, 80}, + {3933, 75}, + {3888, 67}, + {3849, 60}, + {3813, 55}, + {3787, 47}, + {3772, 30}, + {3751, 25}, + {3718, 20}, + {3681, 16}, + {3660, 14}, + {3589, 10}, + {3546, 7}, + {3495, 4}, + {3404, 2}, + {3250, 0}, +}; +#endif +static struct v_to_cap cap_tbl[] = { + {4186, 100}, + {4163, 99}, + {4114, 95}, + {4068, 90}, + {3990, 80}, + {3926, 70}, + {3898, 65}, + {3866, 60}, + {3833, 55}, + {3812, 50}, + {3787, 40}, + {3768, 30}, + {3747, 25}, + {3730, 20}, + {3705, 15}, + {3699, 14}, + {3684, 12}, + {3672, 9}, + {3657, 7}, + {3638, 6}, + {3556, 4}, + {3424, 2}, + {3317, 1}, + {3094, 0}, +}; + +/* + * Note that the res_to_temp table must be strictly sorted by falling + * resistance values to work. + */ +static struct res_to_temp temp_tbl[] = { + {-5, 214834}, + { 0, 162943}, + { 5, 124820}, + {10, 96520}, + {15, 75306}, + {20, 59254}, + {25, 47000}, + {30, 37566}, + {35, 30245}, + {40, 24520}, + {45, 20010}, + {50, 16432}, + {55, 13576}, + {60, 11280}, + {65, 9425}, +}; + +static const struct battery_type bat_type[] = { + [BATTERY_UNKNOWN] = { + /* First element always represent the UNKNOWN battery */ + .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, + .resis_high = 0, + .resis_low = 0, + .battery_resistance = 300, + .charge_full_design = 612, + .nominal_voltage = 3700, + .termination_vol = 4050, + .termination_curr = 200, + .recharge_vol = 3990, + .normal_cur_lvl = 400, + .normal_vol_lvl = 4100, + .maint_a_cur_lvl = 400, + .maint_a_vol_lvl = 4050, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 400, + .maint_b_vol_lvl = 4000, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + +#ifdef CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 53407, + .resis_low = 12500, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_vol = 4130, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_A), + .r_to_t_tbl = temp_tbl_A, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_A), + .v_to_cap_tbl = cap_tbl_A, + + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 165418, + .resis_low = 82869, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_vol = 4130, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_B), + .r_to_t_tbl = temp_tbl_B, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_B), + .v_to_cap_tbl = cap_tbl_B, + }, +#else +/* + * These are the batteries that doesn't have an internal NTC resistor to measure + * its temperature. The temperature in this case is measure with a NTC placed + * near the battery but on the PCB. + */ + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 76000, + .resis_low = 53000, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4130, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 30000, + .resis_low = 10000, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4130, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 95000, + .resis_low = 76001, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4130, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, +#endif +}; + +static char *ab8500_charger_supplied_to[] = { + "ab8500_chargalg", + "ab8500_fg", + "ab8500_btemp", +}; + +static char *ab8500_btemp_supplied_to[] = { + "ab8500_chargalg", + "ab8500_fg", +}; + +static char *ab8500_fg_supplied_to[] = { + "ab8500_chargalg", + "ab8500_usb", +}; + +static char *ab8500_chargalg_supplied_to[] = { + "ab8500_fg", +}; + +struct ab8500_charger_platform_data ab8500_charger_plat_data = { + .supplied_to = ab8500_charger_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_charger_supplied_to), +}; + +struct ab8500_btemp_platform_data ab8500_btemp_plat_data = { + .supplied_to = ab8500_btemp_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_btemp_supplied_to), +}; + +struct ab8500_fg_platform_data ab8500_fg_plat_data = { + .supplied_to = ab8500_fg_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_fg_supplied_to), +}; + +struct ab8500_chargalg_platform_data ab8500_chargalg_plat_data = { + .supplied_to = ab8500_chargalg_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_chargalg_supplied_to), +}; + +static const struct ab8500_bm_capacity_levels cap_levels = { + .critical = 2, + .low = 10, + .normal = 70, + .high = 95, + .full = 100, +}; + +static const struct ab8500_fg_parameters fg = { + .recovery_sleep_timer = 10, + .recovery_total_time = 100, + .init_timer = 1, + .init_discard_time = 5, + .init_total_time = 40, + .high_curr_time = 60, + .accu_charging = 30, + .accu_high_curr = 30, + .high_curr_threshold = 50, + .lowbat_threshold = 3100, +}; + +static const struct ab8500_maxim_parameters maxi_params = { + .ena_maxi = true, + .chg_curr = 910, + .wait_cycles = 10, + .charger_curr_step = 100, +}; + +static const struct ab8500_bm_charger_parameters chg = { + .usb_volt_max = 5500, + .usb_curr_max = 1500, + .ac_volt_max = 7500, + .ac_curr_max = 1500, +}; + +struct ab8500_bm_data ab8500_bm_data = { + .temp_under = 3, + .temp_low = 8, + .temp_high = 43, + .temp_over = 48, + .main_safety_tmr_h = 4, + .temp_interval_chg = 20, + .temp_interval_nochg = 120, + .usb_safety_tmr_h = 4, + .bkup_bat_v = BUP_VCH_SEL_2P6V, + .bkup_bat_i = BUP_ICH_SEL_150UA, + .no_maintenance = false, +#ifdef CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL + .adc_therm = ADC_THERM_BATCTRL, +#else + .adc_therm = ADC_THERM_BATTEMP, +#endif + .chg_unknown_bat = false, + .enable_overshoot = false, + .fg_res = 10, + .cap_levels = &cap_levels, + .bat_type = bat_type, + .n_btypes = ARRAY_SIZE(bat_type), + .batt_id = 0, + .interval_charging = 5, + .interval_not_charging = 120, + .temp_hysteresis = 3, + .gnd_lift_resistance = 34, + .maxi = &maxi_params, + .chg_params = &chg, + .fg_params = &fg, +}; diff --git a/arch/arm/mach-ux500/board-mop500-bm.h b/arch/arm/mach-ux500/board-mop500-bm.h new file mode 100644 index 00000000000..eb2450f1ab5 --- /dev/null +++ b/arch/arm/mach-ux500/board-mop500-bm.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U8500 board specific charger and battery initialization parameters. + * + * Author: Johan Palsson <johan.palsson@stericsson.com> for ST-Ericsson. + * Author: Johan Gardsmark <johan.gardsmark@stericsson.com> for ST-Ericsson. + * + */ + +#ifndef __BOARD_MOP500_BM_H +#define __BOARD_MOP500_BM_H + +#include <linux/mfd/ab8500/bm.h> + +extern struct ab8500_charger_platform_data ab8500_charger_plat_data; +extern struct ab8500_btemp_platform_data ab8500_btemp_plat_data; +extern struct ab8500_fg_platform_data ab8500_fg_plat_data; +extern struct ab8500_chargalg_platform_data ab8500_chargalg_plat_data; +extern struct ab8500_bm_data ab8500_bm_data; + +#endif diff --git a/arch/arm/mach-ux500/board-mop500-cyttsp.c b/arch/arm/mach-ux500/board-mop500-cyttsp.c new file mode 100755 index 00000000000..2aa27ea9b1f --- /dev/null +++ b/arch/arm/mach-ux500/board-mop500-cyttsp.c @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2011 ST-Ericsson SA + * Author: Avinash A <avinash.a@stericsson.com> for ST-Ericsson + * License terms:GNU General Public License (GPL) version 2 + */ +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/gpio.h> +#include <linux/cyttsp.h> +#include <linux/delay.h> +#include <linux/gpio/nomadik.h> +#include <linux/i2c.h> +#include <linux/input/matrix_keypad.h> +#include <linux/mfd/tc3589x.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/amba/pl022.h> +#include <plat/pincfg.h> +#include <mach/hardware.h> +#include <mach/irqs.h> +#include <mach/irqs-db8500.h> +#include "pins-db8500.h" +#include "board-mop500.h" +#include "devices-db8500.h" + +#define NUM_SSP_CLIENTS 10 + +/* cyttsp_gpio_board_init : configures the touch panel. */ +static int cyttsp_plat_init(int on) +{ + int ret; + + ret = gpio_direction_output(CYPRESS_SLAVE_SELECT_GPIO, 1); + if (ret < 0) { + pr_err("slave select gpio direction failed\n"); + gpio_free(CYPRESS_SLAVE_SELECT_GPIO); + return ret; + } + return 0; +} + +static struct pl022_ssp_controller mop500_spi2_data = { + .bus_id = SPI023_2_CONTROLLER, + .num_chipselect = NUM_SSP_CLIENTS, +}; + +static int cyttsp_wakeup(void) +{ + int ret; + + ret = gpio_request(CYPRESS_TOUCH_INT_PIN, "Wakeup_pin"); + if (ret < 0) { + pr_err("touch gpio failed\n"); + return ret; + } + ret = gpio_direction_output(CYPRESS_TOUCH_INT_PIN, 1); + if (ret < 0) { + pr_err("touch gpio direction failed\n"); + goto out; + } + gpio_set_value(CYPRESS_TOUCH_INT_PIN, 0); + gpio_set_value(CYPRESS_TOUCH_INT_PIN, 1); + /* + * To wake up the controller from sleep + * state the interrupt pin needs to be + * pulsed twice with a delay greater + * than 2 micro seconds. + */ + udelay(3); + gpio_set_value(CYPRESS_TOUCH_INT_PIN, 0); + gpio_set_value(CYPRESS_TOUCH_INT_PIN, 1); + ret = gpio_direction_input(CYPRESS_TOUCH_INT_PIN); + if (ret < 0) { + pr_err("touch gpio direction IN config failed\n"); + goto out; + } +out: + gpio_free(CYPRESS_TOUCH_INT_PIN); + return 0; +} +struct cyttsp_platform_data cyttsp_platdata = { + .maxx = 480, + .maxy = 854, + .flags = 0, + .gen = CY_GEN3, + .use_st = 0, + .use_mt = 1, + .use_trk_id = 0, + .use_hndshk = 0, + .use_sleep = 1, + .use_gestures = 0, + .use_load_file = 0, + .use_force_fw_update = 0, + .use_virtual_keys = 0, + /* activate up to 4 groups and set active distance */ + .gest_set = CY_GEST_GRP_NONE | CY_ACT_DIST, + /* change scn_type to enable finger and/or stylus detection */ + .scn_typ = 0xA5, /* autodetect finger+stylus; balanced mutual scan */ + .act_intrvl = CY_ACT_INTRVL_DFLT, /* Active refresh interval; ms */ + .tch_tmout = CY_TCH_TMOUT_DFLT, /* Active touch timeout; ms */ + .lp_intrvl = CY_LP_INTRVL_DFLT, /* Low power refresh interval; ms */ + .init = cyttsp_plat_init, + .mt_sync = input_mt_sync, + .wakeup = cyttsp_wakeup, + .name = CY_SPI_NAME, + .irq_gpio = CYPRESS_TOUCH_INT_PIN, + .rst_gpio = CYPRESS_TOUCH_RST_GPIO, +}; + +static void cyttsp_spi_cs_control(u32 command) +{ + if (command == SSP_CHIP_SELECT) + gpio_set_value(CYPRESS_SLAVE_SELECT_GPIO, 0); + else if (command == SSP_CHIP_DESELECT) + gpio_set_value(CYPRESS_SLAVE_SELECT_GPIO, 1); +} + +static struct pl022_config_chip cyttsp_ssp_config_chip = { + .com_mode = INTERRUPT_TRANSFER, + .iface = SSP_INTERFACE_MOTOROLA_SPI, + /* we can act as master only */ + .hierarchy = SSP_MASTER, + .slave_tx_disable = 0, + .rx_lev_trig = SSP_RX_1_OR_MORE_ELEM, + .tx_lev_trig = SSP_TX_16_OR_MORE_EMPTY_LOC, + .ctrl_len = SSP_BITS_16, + .wait_state = SSP_MWIRE_WAIT_ZERO, + .duplex = SSP_MICROWIRE_CHANNEL_FULL_DUPLEX, + .cs_control = cyttsp_spi_cs_control, +}; + +static struct spi_board_info cypress_spi_devices[] = { + { + .modalias = CY_SPI_NAME, + .controller_data = &cyttsp_ssp_config_chip, + .platform_data = &cyttsp_platdata, + .max_speed_hz = 1000000, + .bus_num = SPI023_2_CONTROLLER, + .chip_select = 0, + .mode = SPI_MODE_0, + } +}; + +/* + * TC35893 + */ +static const unsigned int sony_keymap[] = { + KEY(3, 1, KEY_END), + KEY(4, 1, KEY_HOME), + KEY(6, 4, KEY_VOLUMEDOWN), + KEY(4, 2, KEY_EMAIL), + KEY(3, 3, KEY_RIGHT), + KEY(2, 5, KEY_BACKSPACE), + + KEY(6, 7, KEY_MENU), + KEY(5, 0, KEY_ENTER), + KEY(4, 3, KEY_0), + KEY(3, 4, KEY_DOT), + KEY(5, 2, KEY_UP), + KEY(3, 5, KEY_DOWN), + + KEY(4, 5, KEY_SEND), + KEY(0, 5, KEY_BACK), + KEY(6, 2, KEY_VOLUMEUP), + KEY(1, 3, KEY_SPACE), + KEY(7, 6, KEY_LEFT), + KEY(5, 5, KEY_SEARCH), +}; + +static struct matrix_keymap_data sony_keymap_data = { + .keymap = sony_keymap, + .keymap_size = ARRAY_SIZE(sony_keymap), +}; + +static struct tc3589x_keypad_platform_data tc35893_data = { + .krow = TC_KPD_ROWS, + .kcol = TC_KPD_COLUMNS, + .debounce_period = TC_KPD_DEBOUNCE_PERIOD, + .settle_time = TC_KPD_SETTLE_TIME, + .irqtype = IRQF_TRIGGER_FALLING, + .enable_wakeup = true, + .keymap_data = &sony_keymap_data, + .no_autorepeat = true, +}; + +static struct tc3589x_platform_data tc3589x_keypad_data = { + .block = TC3589x_BLOCK_KEYPAD, + .keypad = &tc35893_data, + .irq_base = MOP500_EGPIO_IRQ_BASE, +}; + +static struct i2c_board_info __initdata mop500_i2c0_devices_u8500[] = { + { + I2C_BOARD_INFO("tc3589x", 0x44), + .platform_data = &tc3589x_keypad_data, + .irq = NOMADIK_GPIO_TO_IRQ(64), + .flags = I2C_CLIENT_WAKE, + }, +}; + +void mop500_cyttsp_init(void) +{ + int ret = 0; + + /* + * Enable the alternative C function + * in the PRCMU register + */ + prcmu_enable_spi2(); + ret = gpio_request(CYPRESS_SLAVE_SELECT_GPIO, "slave_select_gpio"); + if (ret < 0) + pr_err("slave select gpio failed\n"); + spi_register_board_info(cypress_spi_devices, + ARRAY_SIZE(cypress_spi_devices)); +} + +void __init mop500_u8500uib_r3_init(void) +{ + mop500_cyttsp_init(); + db8500_add_spi2(&mop500_spi2_data); + nmk_config_pin((GPIO64_GPIO | PIN_INPUT_PULLUP), false); + mop500_uib_i2c_add(0, mop500_i2c0_devices_u8500, + ARRAY_SIZE(mop500_i2c0_devices_u8500)); + mop500_uib_i2c_add(0, mop500_i2c0_devices_u8500, + ARRAY_SIZE(mop500_i2c0_devices_u8500)); +} diff --git a/arch/arm/mach-ux500/board-mop500-pins.c b/arch/arm/mach-ux500/board-mop500-pins.c index f26fd76f72b..3d32e9b059e 100644 --- a/arch/arm/mach-ux500/board-mop500-pins.c +++ b/arch/arm/mach-ux500/board-mop500-pins.c @@ -7,24 +7,29 @@ #include <linux/kernel.h> #include <linux/init.h> #include <linux/gpio.h> +#include <linux/io.h> +#include <linux/string.h> #include <asm/mach-types.h> #include <plat/pincfg.h> +#include <linux/gpio/nomadik.h> + #include <mach/hardware.h> +#include <mach/suspend.h> #include "pins-db8500.h" +#include "pins.h" -static pin_cfg_t mop500_pins_common[] = { - /* I2C */ - GPIO147_I2C0_SCL, - GPIO148_I2C0_SDA, - GPIO16_I2C1_SCL, - GPIO17_I2C1_SDA, - GPIO10_I2C2_SDA, - GPIO11_I2C2_SCL, - GPIO229_I2C3_SDA, - GPIO230_I2C3_SCL, +#include "board-pins-sleep-force.h" +enum custom_pin_cfg_t { + PINS_FOR_DEFAULT, + PINS_FOR_U9500, +}; + +static enum custom_pin_cfg_t pinsfor; + +static pin_cfg_t mop500_pins_common[] = { /* MSP0 */ GPIO12_MSP0_TXD, GPIO13_MSP0_TFS, @@ -32,83 +37,20 @@ static pin_cfg_t mop500_pins_common[] = { GPIO15_MSP0_RXD, /* MSP2: HDMI */ - GPIO193_MSP2_TXD, - GPIO194_MSP2_TCK, - GPIO195_MSP2_TFS, + GPIO193_MSP2_TXD | PIN_INPUT_PULLDOWN, + GPIO194_MSP2_TCK | PIN_INPUT_PULLDOWN, + GPIO195_MSP2_TFS | PIN_INPUT_PULLDOWN, GPIO196_MSP2_RXD | PIN_OUTPUT_LOW, + /* LCD TE0 */ + GPIO68_LCD_VSI0 | PIN_INPUT_PULLUP, + /* Touch screen INTERFACE */ GPIO84_GPIO | PIN_INPUT_PULLUP, /* TOUCH_INT1 */ /* STMPE1601/tc35893 keypad IRQ */ GPIO218_GPIO | PIN_INPUT_PULLUP, - /* MMC0 (MicroSD card) */ - GPIO18_MC0_CMDDIR | PIN_OUTPUT_HIGH, - GPIO19_MC0_DAT0DIR | PIN_OUTPUT_HIGH, - GPIO20_MC0_DAT2DIR | PIN_OUTPUT_HIGH, - - GPIO22_MC0_FBCLK | PIN_INPUT_NOPULL, - GPIO23_MC0_CLK | PIN_OUTPUT_LOW, - GPIO24_MC0_CMD | PIN_INPUT_PULLUP, - GPIO25_MC0_DAT0 | PIN_INPUT_PULLUP, - GPIO26_MC0_DAT1 | PIN_INPUT_PULLUP, - GPIO27_MC0_DAT2 | PIN_INPUT_PULLUP, - GPIO28_MC0_DAT3 | PIN_INPUT_PULLUP, - - /* SDI1 (SDIO) */ - GPIO208_MC1_CLK | PIN_OUTPUT_LOW, - GPIO209_MC1_FBCLK | PIN_INPUT_NOPULL, - GPIO210_MC1_CMD | PIN_INPUT_PULLUP, - GPIO211_MC1_DAT0 | PIN_INPUT_PULLUP, - GPIO212_MC1_DAT1 | PIN_INPUT_PULLUP, - GPIO213_MC1_DAT2 | PIN_INPUT_PULLUP, - GPIO214_MC1_DAT3 | PIN_INPUT_PULLUP, - - /* MMC2 (On-board DATA INTERFACE eMMC) */ - GPIO128_MC2_CLK | PIN_OUTPUT_LOW, - GPIO129_MC2_CMD | PIN_INPUT_PULLUP, - GPIO130_MC2_FBCLK | PIN_INPUT_NOPULL, - GPIO131_MC2_DAT0 | PIN_INPUT_PULLUP, - GPIO132_MC2_DAT1 | PIN_INPUT_PULLUP, - GPIO133_MC2_DAT2 | PIN_INPUT_PULLUP, - GPIO134_MC2_DAT3 | PIN_INPUT_PULLUP, - GPIO135_MC2_DAT4 | PIN_INPUT_PULLUP, - GPIO136_MC2_DAT5 | PIN_INPUT_PULLUP, - GPIO137_MC2_DAT6 | PIN_INPUT_PULLUP, - GPIO138_MC2_DAT7 | PIN_INPUT_PULLUP, - - /* MMC4 (On-board STORAGE INTERFACE eMMC) */ - GPIO197_MC4_DAT3 | PIN_INPUT_PULLUP, - GPIO198_MC4_DAT2 | PIN_INPUT_PULLUP, - GPIO199_MC4_DAT1 | PIN_INPUT_PULLUP, - GPIO200_MC4_DAT0 | PIN_INPUT_PULLUP, - GPIO201_MC4_CMD | PIN_INPUT_PULLUP, - GPIO202_MC4_FBCLK | PIN_INPUT_NOPULL, - GPIO203_MC4_CLK | PIN_OUTPUT_LOW, - GPIO204_MC4_DAT7 | PIN_INPUT_PULLUP, - GPIO205_MC4_DAT6 | PIN_INPUT_PULLUP, - GPIO206_MC4_DAT5 | PIN_INPUT_PULLUP, - GPIO207_MC4_DAT4 | PIN_INPUT_PULLUP, - - /* SKE keypad */ - GPIO153_KP_I7, - GPIO154_KP_I6, - GPIO155_KP_I5, - GPIO156_KP_I4, - GPIO157_KP_O7, - GPIO158_KP_O6, - GPIO159_KP_O5, - GPIO160_KP_O4, - GPIO161_KP_I3, - GPIO162_KP_I2, - GPIO163_KP_I1, - GPIO164_KP_I0, - GPIO165_KP_O3, - GPIO166_KP_O2, - GPIO167_KP_O1, - GPIO168_KP_O0, - /* UART */ /* uart-0 pins gpio configuration should be * kept intact to prevent glitch in tx line @@ -128,9 +70,14 @@ static pin_cfg_t mop500_pins_common[] = { GPIO31_U2_CTSn | PIN_INPUT_PULLUP, GPIO32_U2_RTSn | PIN_OUTPUT_HIGH, - /* Display & HDMI HW sync */ - GPIO68_LCD_VSI0 | PIN_INPUT_PULLUP, - GPIO69_LCD_VSI1 | PIN_INPUT_PULLUP, + /* HSI */ + GPIO219_HSIR_FLA0, + GPIO220_HSIR_DAT0, + GPIO221_HSIR_RDY0, + GPIO222_HSIT_FLA0, + GPIO223_HSIT_DAT0, + GPIO224_HSIT_RDY0, + GPIO225_GPIO | PIN_INPUT_PULLDOWN, /* CA_WAKE0 */ }; static pin_cfg_t mop500_pins_default[] = { @@ -140,10 +87,13 @@ static pin_cfg_t mop500_pins_default[] = { GPIO145_SSP0_RXD | PIN_PULL_DOWN, GPIO146_SSP0_TXD, + /* XENON Flashgun INTERFACE */ + GPIO6_IP_GPIO0 | PIN_INPUT_PULLUP,/* XENON_FLASH_ID */ + GPIO7_IP_GPIO1 | PIN_INPUT_PULLUP,/* XENON_READY */ GPIO217_GPIO | PIN_INPUT_PULLUP, /* TC35892 IRQ */ - /* SDI0 (MicroSD card) */ + /* sdi0 (removable MMC/SD/SDIO cards) not handled by pm_runtime */ GPIO21_MC0_DAT31DIR | PIN_OUTPUT_HIGH, /* UART */ @@ -155,13 +105,11 @@ static pin_cfg_t mop500_pins_default[] = { static pin_cfg_t mop500_pins_hrefv60[] = { /* WLAN */ - GPIO4_GPIO | PIN_INPUT_PULLUP,/* WLAN_IRQ */ GPIO85_GPIO | PIN_OUTPUT_LOW,/* WLAN_ENA */ /* XENON Flashgun INTERFACE */ GPIO6_IP_GPIO0 | PIN_INPUT_PULLUP,/* XENON_FLASH_ID */ GPIO7_IP_GPIO1 | PIN_INPUT_PULLUP,/* XENON_READY */ - GPIO170_GPIO | PIN_OUTPUT_LOW, /* XENON_CHARGE */ /* Assistant LED INTERFACE */ GPIO21_GPIO | PIN_OUTPUT_LOW, /* XENON_EN1 */ @@ -172,7 +120,7 @@ static pin_cfg_t mop500_pins_hrefv60[] = { GPIO32_GPIO | PIN_INPUT_PULLDOWN, /* Magnetometer DRDY */ /* Display Interface */ - GPIO65_GPIO | PIN_OUTPUT_LOW, /* DISP1 RST */ + GPIO65_GPIO | PIN_OUTPUT_HIGH, /* DISP1 NO RST */ GPIO66_GPIO | PIN_OUTPUT_LOW, /* DISP2 RST */ /* Touch screen INTERFACE */ @@ -218,7 +166,7 @@ static pin_cfg_t mop500_pins_hrefv60[] = { GPIO145_GPIO | PIN_INPUT_PULLDOWN,/* HAL_SW */ /* Audio Amplifier Interface */ - GPIO149_GPIO | PIN_OUTPUT_LOW, /* VAUDIO_HF_EN */ + GPIO149_GPIO | PIN_OUTPUT_HIGH, /* VAUDIO_HF_EN, enable MAX8968 */ /* GBF INTERFACE */ GPIO171_GPIO | PIN_OUTPUT_LOW, /* GBF_ENA_RESET */ @@ -233,7 +181,20 @@ static pin_cfg_t mop500_pins_hrefv60[] = { /* Proximity Sensor */ GPIO217_GPIO | PIN_INPUT_PULLUP, + /* SD card detect */ + GPIO95_GPIO | PIN_INPUT_PULLUP, +}; +static pin_cfg_t u9500_pins[] = { + GPIO4_U1_RXD | PIN_INPUT_PULLUP, + GPIO5_U1_TXD | PIN_OUTPUT_HIGH, + GPIO144_GPIO | PIN_INPUT_PULLUP,/* WLAN_IRQ */ + GPIO226_GPIO | PIN_OUTPUT_HIGH, /* HSI AC_WAKE0 */ +}; + +static pin_cfg_t u8500_pins[] = { + GPIO226_GPIO | PIN_OUTPUT_LOW, /* WLAN_PMU_EN */ + GPIO4_GPIO | PIN_INPUT_PULLUP,/* WLAN_IRQ */ }; static pin_cfg_t snowball_pins[] = { @@ -274,12 +235,704 @@ static pin_cfg_t snowball_pins[] = { /* RSTn_LAN */ GPIO141_GPIO | PIN_OUTPUT_HIGH, + + /* Accelerometer/Magnetometer */ + GPIO163_GPIO | PIN_INPUT_PULLUP, /* ACCEL_IRQ1 */ + GPIO164_GPIO | PIN_INPUT_PULLUP, /* ACCEL_IRQ2 */ + GPIO165_GPIO | PIN_INPUT_PULLUP, /* MAG_DRDY */ + + /* WLAN/GBF */ + GPIO171_GPIO | PIN_OUTPUT_HIGH,/* GBF_ENA */ + GPIO215_GPIO | PIN_OUTPUT_LOW,/* WLAN_ENA */ + GPIO216_GPIO | PIN_INPUT_PULLUP,/* WLAN_IRQ */ }; +/* + * I2C + */ + +static UX500_PINS(mop500_pins_i2c0, + GPIO147_I2C0_SCL, + GPIO148_I2C0_SDA, +); + +static UX500_PINS(mop500_pins_i2c1, + GPIO16_I2C1_SCL, + GPIO17_I2C1_SDA, +); + +static UX500_PINS(mop500_pins_i2c2, + GPIO10_I2C2_SDA, + GPIO11_I2C2_SCL, +); + +static UX500_PINS(mop500_pins_i2c3, + GPIO229_I2C3_SDA, + GPIO230_I2C3_SCL, +); + +static UX500_PINS(mop500_pins_mcde_dpi, + GPIO64_LCDB_DE, + GPIO65_LCDB_HSO, + GPIO66_LCDB_VSO, + GPIO67_LCDB_CLK, + GPIO70_LCD_D0, + GPIO71_LCD_D1, + GPIO72_LCD_D2, + GPIO73_LCD_D3, + GPIO74_LCD_D4, + GPIO75_LCD_D5, + GPIO76_LCD_D6, + GPIO77_LCD_D7, + GPIO153_LCD_D24, + GPIO154_LCD_D25, + GPIO155_LCD_D26, + GPIO156_LCD_D27, + GPIO157_LCD_D28, + GPIO158_LCD_D29, + GPIO159_LCD_D30, + GPIO160_LCD_D31, + GPIO161_LCD_D32, + GPIO162_LCD_D33, + GPIO163_LCD_D34, + GPIO164_LCD_D35, + GPIO165_LCD_D36, + GPIO166_LCD_D37, + GPIO167_LCD_D38, + GPIO168_LCD_D39, +); + +static UX500_PINS(mop500_pins_mcde_tvout, + GPIO78_LCD_D8, + GPIO79_LCD_D9, + GPIO80_LCD_D10, + GPIO81_LCD_D11, + GPIO150_LCDA_CLK, +); + +static UX500_PINS(mop500_pins_mcde_hdmi, + GPIO69_LCD_VSI1 | PIN_INPUT_PULLUP, +); + +static UX500_PINS(mop500_pins_ske, + GPIO153_KP_I7 | PIN_INPUT_PULLDOWN | PIN_SLPM_INPUT_PULLUP, + GPIO154_KP_I6 | PIN_INPUT_PULLDOWN | PIN_SLPM_INPUT_PULLUP, + GPIO155_KP_I5 | PIN_INPUT_PULLDOWN | PIN_SLPM_INPUT_PULLUP, + GPIO156_KP_I4 | PIN_INPUT_PULLDOWN | PIN_SLPM_INPUT_PULLUP, + GPIO161_KP_I3 | PIN_INPUT_PULLDOWN | PIN_SLPM_INPUT_PULLUP, + GPIO162_KP_I2 | PIN_INPUT_PULLDOWN | PIN_SLPM_INPUT_PULLUP, + GPIO163_KP_I1 | PIN_INPUT_PULLDOWN | PIN_SLPM_INPUT_PULLUP, + GPIO164_KP_I0 | PIN_INPUT_PULLDOWN | PIN_SLPM_INPUT_PULLUP, + GPIO157_KP_O7 | PIN_INPUT_PULLUP | PIN_SLPM_OUTPUT_LOW, + GPIO158_KP_O6 | PIN_INPUT_PULLUP | PIN_SLPM_OUTPUT_LOW, + GPIO159_KP_O5 | PIN_INPUT_PULLUP | PIN_SLPM_OUTPUT_LOW, + GPIO160_KP_O4 | PIN_INPUT_PULLUP | PIN_SLPM_OUTPUT_LOW, + GPIO165_KP_O3 | PIN_INPUT_PULLUP | PIN_SLPM_OUTPUT_LOW, + GPIO166_KP_O2 | PIN_INPUT_PULLUP | PIN_SLPM_OUTPUT_LOW, + GPIO167_KP_O1 | PIN_INPUT_PULLUP | PIN_SLPM_OUTPUT_LOW, + GPIO168_KP_O0 | PIN_INPUT_PULLUP | PIN_SLPM_OUTPUT_LOW, +); + +/* sdi0 (removable MMC/SD/SDIO cards) */ +static UX500_PINS(mop500_pins_sdi0, + GPIO18_MC0_CMDDIR | PIN_OUTPUT_HIGH, + GPIO19_MC0_DAT0DIR | PIN_OUTPUT_HIGH, + GPIO20_MC0_DAT2DIR | PIN_OUTPUT_HIGH, + + GPIO22_MC0_FBCLK | PIN_INPUT_NOPULL, + GPIO23_MC0_CLK | PIN_OUTPUT_LOW, + GPIO24_MC0_CMD | PIN_INPUT_PULLUP, + GPIO25_MC0_DAT0 | PIN_INPUT_PULLUP, + GPIO26_MC0_DAT1 | PIN_INPUT_PULLUP, + GPIO27_MC0_DAT2 | PIN_INPUT_PULLUP, + GPIO28_MC0_DAT3 | PIN_INPUT_PULLUP, +); + +/* sdi1 (WLAN CW1200) */ +static UX500_PINS(mop500_pins_sdi1, + GPIO208_MC1_CLK | PIN_OUTPUT_LOW, + GPIO209_MC1_FBCLK | PIN_INPUT_NOPULL, + GPIO210_MC1_CMD | PIN_INPUT_PULLUP, + GPIO211_MC1_DAT0 | PIN_INPUT_PULLUP, + GPIO212_MC1_DAT1 | PIN_INPUT_PULLUP, + GPIO213_MC1_DAT2 | PIN_INPUT_PULLUP, + GPIO214_MC1_DAT3 | PIN_INPUT_PULLUP, +); + +/* sdi2 (POP eMMC) */ +static UX500_PINS(mop500_pins_sdi2, + GPIO128_MC2_CLK | PIN_OUTPUT_LOW, + GPIO129_MC2_CMD | PIN_INPUT_PULLUP, + GPIO130_MC2_FBCLK | PIN_INPUT_NOPULL, + GPIO131_MC2_DAT0 | PIN_INPUT_PULLUP, + GPIO132_MC2_DAT1 | PIN_INPUT_PULLUP, + GPIO133_MC2_DAT2 | PIN_INPUT_PULLUP, + GPIO134_MC2_DAT3 | PIN_INPUT_PULLUP, + GPIO135_MC2_DAT4 | PIN_INPUT_PULLUP, + GPIO136_MC2_DAT5 | PIN_INPUT_PULLUP, + GPIO137_MC2_DAT6 | PIN_INPUT_PULLUP, + GPIO138_MC2_DAT7 | PIN_INPUT_PULLUP, +); + +/* sdi4 (PCB eMMC) */ +static UX500_PINS(mop500_pins_sdi4, + GPIO197_MC4_DAT3 | PIN_INPUT_PULLUP, + GPIO198_MC4_DAT2 | PIN_INPUT_PULLUP, + GPIO199_MC4_DAT1 | PIN_INPUT_PULLUP, + GPIO200_MC4_DAT0 | PIN_INPUT_PULLUP, + GPIO201_MC4_CMD | PIN_INPUT_PULLUP, + GPIO202_MC4_FBCLK | PIN_INPUT_NOPULL, + GPIO203_MC4_CLK | PIN_OUTPUT_LOW, + GPIO204_MC4_DAT7 | PIN_INPUT_PULLUP, + GPIO205_MC4_DAT6 | PIN_INPUT_PULLUP, + GPIO206_MC4_DAT5 | PIN_INPUT_PULLUP, + GPIO207_MC4_DAT4 | PIN_INPUT_PULLUP, +); + +/* USB */ +static UX500_PINS(mop500_pins_usb, + GPIO256_USB_NXT, + GPIO257_USB_STP | PIN_OUTPUT_HIGH, + GPIO258_USB_XCLK, + GPIO259_USB_DIR, + GPIO260_USB_DAT7, + GPIO261_USB_DAT6, + GPIO262_USB_DAT5, + GPIO263_USB_DAT4, + GPIO264_USB_DAT3, + GPIO265_USB_DAT2, + GPIO266_USB_DAT1, + GPIO267_USB_DAT0, +); + +/* SPI2 */ +static UX500_PINS(mop500_pins_spi2, + GPIO216_GPIO | PIN_OUTPUT_HIGH, + GPIO218_SPI2_RXD | PIN_INPUT_PULLDOWN, + GPIO215_SPI2_TXD | PIN_OUTPUT_LOW, + GPIO217_SPI2_CLK | PIN_OUTPUT_LOW, +); + +static struct ux500_pin_lookup mop500_pins[] = { + PIN_LOOKUP("mcde-dpi", &mop500_pins_mcde_dpi), + PIN_LOOKUP("mcde-tvout", &mop500_pins_mcde_tvout), + PIN_LOOKUP("av8100-hdmi", &mop500_pins_mcde_hdmi), + PIN_LOOKUP("nmk-i2c.0", &mop500_pins_i2c0), + PIN_LOOKUP("nmk-i2c.1", &mop500_pins_i2c1), + PIN_LOOKUP("nmk-i2c.2", &mop500_pins_i2c2), + PIN_LOOKUP("nmk-i2c.3", &mop500_pins_i2c3), + PIN_LOOKUP("ske", &mop500_pins_ske), + PIN_LOOKUP("sdi0", &mop500_pins_sdi0), + PIN_LOOKUP("sdi1", &mop500_pins_sdi1), + PIN_LOOKUP("sdi2", &mop500_pins_sdi2), + PIN_LOOKUP("sdi4", &mop500_pins_sdi4), + PIN_LOOKUP("ab8500-usb.0", &mop500_pins_usb), + PIN_LOOKUP("spi2", &mop500_pins_spi2), +}; + +/* + * Sleep pin configuration for u8500 platform. + * If another HW is used the GPIO's must be configured + * correctly when entering sleep for optimal power + * consumption. + */ +static pin_cfg_t mop500_pins_common_power_save_bank0[] = { + GPIO0_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO1_GPIO | PIN_SLPM_OUTPUT_HIGH | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO2_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO3_GPIO | PIN_SLPM_DIR_OUTPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO4_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO5_GPIO | PIN_SLPM_OUTPUT_HIGH | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO6_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO7_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO8_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO9_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO10_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO11_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO12_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO13_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO14_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO15_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO16_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO17_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO18_GPIO | PIN_SLPM_OUTPUT_HIGH | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO19_GPIO | PIN_SLPM_OUTPUT_HIGH | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO20_GPIO | PIN_SLPM_OUTPUT_HIGH | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO21_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO22_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO23_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO24_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO25_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO26_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO27_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO28_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO29_U2_RXD | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + GPIO30_U2_TXD | PIN_SLPM_DIR_OUTPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO31_U2_CTSn | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank0_href60[] = { + GPIO0_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO1_GPIO | PIN_SLPM_OUTPUT_HIGH | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO2_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO3_GPIO | PIN_SLPM_DIR_OUTPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO4_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO5_GPIO | PIN_SLPM_OUTPUT_HIGH | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO6_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO7_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO8_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO9_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO10_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO11_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO12_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO13_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO14_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO15_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO16_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO17_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO18_GPIO | PIN_SLPM_OUTPUT_HIGH | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO19_GPIO | PIN_SLPM_OUTPUT_HIGH | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO20_GPIO | PIN_SLPM_OUTPUT_HIGH | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO21_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO22_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO23_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO24_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO25_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO26_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO27_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO28_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO29_U2_RXD | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + GPIO30_U2_TXD | PIN_SLPM_DIR_OUTPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO31_U2_CTSn | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank1[] = { + GPIO32_U2_RTSn | PIN_SLPM_DIR_OUTPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO33_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO34_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO35_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO36_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank2[] = { + GPIO64_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO65_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO66_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO67_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO68_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO69_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO70_STMAPE_CLK | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_DISABLE | PIN_SLPM_PDIS_DISABLED, + GPIO71_STMAPE_DAT3 | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_DISABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO72_STMAPE_DAT2 | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_DISABLE | PIN_SLPM_PDIS_DISABLED, + GPIO73_STMAPE_DAT1 | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_DISABLE | PIN_SLPM_PDIS_DISABLED, + GPIO74_STMAPE_DAT0 | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_DISABLE | PIN_SLPM_PDIS_DISABLED, + GPIO75_U2_RXD | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO76_U2_TXD | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO77_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO78_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO79_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO80_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO81_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO82_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO83_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO84_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO85_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO86_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO87_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO88_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO89_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO90_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO91_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO92_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO93_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO94_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO95_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank2_href60[] = { + GPIO64_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO65_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO66_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO67_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO68_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO69_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO70_STMAPE_CLK | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_DISABLE | PIN_SLPM_PDIS_DISABLED, + GPIO71_STMAPE_DAT3 | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_DISABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO72_STMAPE_DAT2 | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_DISABLE | PIN_SLPM_PDIS_DISABLED, + GPIO73_STMAPE_DAT1 | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_DISABLE | PIN_SLPM_PDIS_DISABLED, + GPIO74_STMAPE_DAT0 | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_DISABLE | PIN_SLPM_PDIS_DISABLED, + GPIO75_U2_RXD | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO76_U2_TXD | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO77_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO78_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO79_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO80_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO81_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO82_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO83_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO84_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO85_GPIO, + GPIO86_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO87_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO88_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO89_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO90_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO91_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO92_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO93_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO94_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO95_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank3[] = { + GPIO96_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO97_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank4[] = { + GPIO128_MC2_CLK | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO129_MC2_CMD | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + GPIO130_MC2_FBCLK | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + GPIO131_MC2_DAT0 | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO132_MC2_DAT1 | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO133_MC2_DAT2 | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO134_MC2_DAT3 | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO135_MC2_DAT4 | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO136_MC2_DAT5 | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO137_MC2_DAT6 | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO138_MC2_DAT7 | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO139_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO140_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO141_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO142_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO143_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO144_GPIO | PIN_SLPM_OUTPUT_HIGH | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO145_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO146_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO147_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO148_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO149_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO150_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO151_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO152_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO153_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO154_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO155_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO156_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO157_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO158_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO159_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank5[] = { + GPIO160_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO161_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO162_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO163_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO164_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO165_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO166_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO167_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO168_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO169_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO170_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO171_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank5_href60[] = { + GPIO160_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO161_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO162_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO163_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO164_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO165_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO166_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO167_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO168_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO169_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO170_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO171_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank6[] = { + GPIO192_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO193_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO194_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO195_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO196_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO197_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO198_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO199_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO200_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO201_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO202_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO203_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO204_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO205_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO206_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO207_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO208_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO209_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO210_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO211_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO212_GPIO, + GPIO213_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO214_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO215_GPIO, + + GPIO216_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO217_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO218_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO219_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO220_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO221_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO222_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO223_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank6_href60[] = { + GPIO192_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO193_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO194_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO195_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO196_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO197_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO198_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO199_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO200_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO201_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO202_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO203_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO204_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO205_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO206_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO207_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO208_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO209_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO210_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO211_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO212_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO213_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO214_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO215_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO216_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO217_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO218_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO219_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO220_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO221_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO222_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO223_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank7[] = { + GPIO224_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO225_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO226_GPIO | PIN_SLPM_DIR_OUTPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO227_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO228_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO229_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO230_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank7_href60[] = { + GPIO224_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO225_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO226_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO227_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + + GPIO228_GPIO | PIN_SLPM_OUTPUT_LOW | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO229_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO230_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, +}; + +static pin_cfg_t mop500_pins_common_power_save_bank8[] = { + GPIO256_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + GPIO257_GPIO | PIN_SLPM_OUTPUT_HIGH | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_DISABLED, + GPIO258_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + GPIO259_GPIO | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + + GPIO260_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + GPIO261_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + GPIO262_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + GPIO263_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + + GPIO264_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + GPIO265_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + GPIO266_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, + GPIO267_GPIO | PIN_SLPM_DIR_INPUT | PIN_SLPM_WAKEUP_ENABLE | PIN_SLPM_PDIS_ENABLED, +}; + +static void mop500_pins_suspend_force(void) +{ + if (machine_is_hrefv60()) + sleep_pins_config_pm(mop500_pins_common_power_save_bank0_href60, + ARRAY_SIZE(mop500_pins_common_power_save_bank0_href60)); + else + sleep_pins_config_pm(mop500_pins_common_power_save_bank0, + ARRAY_SIZE(mop500_pins_common_power_save_bank0)); + + sleep_pins_config_pm(mop500_pins_common_power_save_bank1, + ARRAY_SIZE(mop500_pins_common_power_save_bank1)); + + if (machine_is_hrefv60()) + sleep_pins_config_pm(mop500_pins_common_power_save_bank2_href60, + ARRAY_SIZE(mop500_pins_common_power_save_bank2_href60)); + else + sleep_pins_config_pm(mop500_pins_common_power_save_bank2, + ARRAY_SIZE(mop500_pins_common_power_save_bank2)); + + sleep_pins_config_pm(mop500_pins_common_power_save_bank3, + ARRAY_SIZE(mop500_pins_common_power_save_bank3)); + + sleep_pins_config_pm(mop500_pins_common_power_save_bank4, + ARRAY_SIZE(mop500_pins_common_power_save_bank4)); + + if (machine_is_hrefv60()) + sleep_pins_config_pm(mop500_pins_common_power_save_bank5_href60, + ARRAY_SIZE(mop500_pins_common_power_save_bank5_href60)); + else + sleep_pins_config_pm(mop500_pins_common_power_save_bank5, + ARRAY_SIZE(mop500_pins_common_power_save_bank5)); + + if (machine_is_hrefv60()) + sleep_pins_config_pm(mop500_pins_common_power_save_bank6_href60, + ARRAY_SIZE(mop500_pins_common_power_save_bank6_href60)); + else + sleep_pins_config_pm(mop500_pins_common_power_save_bank6, + ARRAY_SIZE(mop500_pins_common_power_save_bank6)); + + if (machine_is_hrefv60()) + sleep_pins_config_pm(mop500_pins_common_power_save_bank7_href60, + ARRAY_SIZE(mop500_pins_common_power_save_bank7_href60)); + else + sleep_pins_config_pm(mop500_pins_common_power_save_bank7, + ARRAY_SIZE(mop500_pins_common_power_save_bank7)); + + sleep_pins_config_pm(mop500_pins_common_power_save_bank8, + ARRAY_SIZE(mop500_pins_common_power_save_bank8)); +} + +/* + * This function is called to force gpio power save + * mux settings during suspend. + * This is a temporary solution until all drivers are + * controlling their pin settings when in inactive mode. + */ +static void mop500_pins_suspend_force_mux(void) +{ + sleep_pins_config_pm_mux(mop500_pins_common_power_save_bank0, + ARRAY_SIZE(mop500_pins_common_power_save_bank0)); + + sleep_pins_config_pm_mux(mop500_pins_common_power_save_bank1, + ARRAY_SIZE(mop500_pins_common_power_save_bank1)); + + sleep_pins_config_pm_mux(mop500_pins_common_power_save_bank2, + ARRAY_SIZE(mop500_pins_common_power_save_bank2)); + + sleep_pins_config_pm_mux(mop500_pins_common_power_save_bank3, + ARRAY_SIZE(mop500_pins_common_power_save_bank3)); + + sleep_pins_config_pm_mux(mop500_pins_common_power_save_bank4, + ARRAY_SIZE(mop500_pins_common_power_save_bank4)); + + sleep_pins_config_pm_mux(mop500_pins_common_power_save_bank5, + ARRAY_SIZE(mop500_pins_common_power_save_bank5)); + + sleep_pins_config_pm_mux(mop500_pins_common_power_save_bank6, + ARRAY_SIZE(mop500_pins_common_power_save_bank6)); + + sleep_pins_config_pm_mux(mop500_pins_common_power_save_bank7, + ARRAY_SIZE(mop500_pins_common_power_save_bank7)); + + sleep_pins_config_pm_mux(mop500_pins_common_power_save_bank8, + ARRAY_SIZE(mop500_pins_common_power_save_bank8)); +} + +/* + * passing "pinsfor=" in kernel cmdline allows for custom + * configuration of GPIOs on u8500 derived boards. + */ +static int __init early_pinsfor(char *p) +{ + pinsfor = PINS_FOR_DEFAULT; + + if (strcmp(p, "u9500-21") == 0) + pinsfor = PINS_FOR_U9500; + + return 0; +} +early_param("pinsfor", early_pinsfor); + +int pins_for_u9500(void) +{ + if (pinsfor == PINS_FOR_U9500) + return 1; + + return 0; +} + void __init mop500_pins_init(void) { nmk_config_pins(mop500_pins_common, ARRAY_SIZE(mop500_pins_common)); + + ux500_pins_add(mop500_pins, ARRAY_SIZE(mop500_pins)); + if (machine_is_hrefv60()) nmk_config_pins(mop500_pins_hrefv60, ARRAY_SIZE(mop500_pins_hrefv60)); @@ -289,4 +942,19 @@ void __init mop500_pins_init(void) else nmk_config_pins(mop500_pins_default, ARRAY_SIZE(mop500_pins_default)); + + switch (pinsfor) { + case PINS_FOR_U9500: + nmk_config_pins(u9500_pins, ARRAY_SIZE(u9500_pins)); + break; + + case PINS_FOR_DEFAULT: + nmk_config_pins(u8500_pins, ARRAY_SIZE(u8500_pins)); + default: + break; + } + + suspend_set_pins_force_fn(mop500_pins_suspend_force, + mop500_pins_suspend_force_mux); } + diff --git a/arch/arm/mach-ux500/board-mop500-regulators.c b/arch/arm/mach-ux500/board-mop500-regulators.c index 2735d03996c..10d77fb5cf5 100644 --- a/arch/arm/mach-ux500/board-mop500-regulators.c +++ b/arch/arm/mach-ux500/board-mop500-regulators.c @@ -13,6 +13,48 @@ #include <linux/regulator/ab8500.h> #include "board-mop500-regulators.h" +#ifdef CONFIG_REGULATOR_FIXED_VOLTAGE +/* + * GPIO regulator controlled by the ab8500 GPIO16 + */ +static struct regulator_consumer_supply gpio_wlan_vbat_consumers[] = { + /* for cg2900 chip */ + REGULATOR_SUPPLY("vdd", "cg2900-uart.0"), + /* for cw1200 chip */ + REGULATOR_SUPPLY("vdd", "cw1200_wlan"), +}; + +struct regulator_init_data gpio_wlan_vbat_regulator = { + .constraints = { + .name = "WLAN-VBAT", + .min_uV = 3600000, + .max_uV = 3600000, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(gpio_wlan_vbat_consumers), + .consumer_supplies = gpio_wlan_vbat_consumers, +}; + +/* + * GPIO regulator controlled by the ab8500 GPIO26 + */ +static struct regulator_consumer_supply gpio_en_3v3_consumers[] = { + /* for LAN chip */ + REGULATOR_SUPPLY("vdd33a", "smsc911x.0"), +}; + +struct regulator_init_data gpio_en_3v3_regulator = { + .constraints = { + .name = "EN-3V3", + .min_uV = 3300000, + .max_uV = 3300000, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(gpio_en_3v3_consumers), + .consumer_supplies = gpio_en_3v3_consumers, +}; +#endif + /* * TPS61052 regulator */ @@ -38,21 +80,39 @@ struct regulator_init_data tps61052_regulator = { }; static struct regulator_consumer_supply ab8500_vaux1_consumers[] = { - /* External displays, connector on board 2v5 power supply */ - REGULATOR_SUPPLY("vaux12v5", "mcde.0"), + /* lps001wp baromenter i2c dev name is 2-005c + * maybe change that in the driver, like for lsm303dlh drivers + */ + REGULATOR_SUPPLY("vdd", "2-005c"), + /* Main display, u8500 R3 uib */ + REGULATOR_SUPPLY("vddi", "mcde_disp_sony_acx424akp.0"), + /* Main display, u8500 uib and ST uib */ + REGULATOR_SUPPLY("vdd1", "samsung_s6d16d0.0"), + /* Secondary display, ST uib */ + REGULATOR_SUPPLY("vdd1", "samsung_s6d16d0.1"), /* SFH7741 proximity sensor */ REGULATOR_SUPPLY("vcc", "gpio-keys.0"), /* BH1780GLS ambient light sensor */ REGULATOR_SUPPLY("vcc", "2-0029"), /* lsm303dlh accelerometer */ - REGULATOR_SUPPLY("vdd", "3-0018"), + REGULATOR_SUPPLY("vdd", "lsm303dlh.0"), /* lsm303dlh magnetometer */ - REGULATOR_SUPPLY("vdd", "3-001e"), + REGULATOR_SUPPLY("vdd", "lsm303dlh.1"), /* Rohm BU21013 Touchscreen devices */ REGULATOR_SUPPLY("avdd", "3-005c"), REGULATOR_SUPPLY("avdd", "3-005d"), /* Synaptics RMI4 Touchscreen device */ REGULATOR_SUPPLY("vdd", "3-004b"), + /* L3G4200D Gyroscope device */ + REGULATOR_SUPPLY("vdd", "l3g4200d"), + /* Proximity and Hal sensor device */ + REGULATOR_SUPPLY("vdd", "sensor1p.0"), + /* Ambient light sensor device */ + REGULATOR_SUPPLY("vdd", "3-0029"), + /* Cypress TrueTouch Touchscreen device */ + REGULATOR_SUPPLY("vcpin", "spi8.0"), + /* Camera device */ + REGULATOR_SUPPLY("vaux12v5", "mmio_camera"), }; static struct regulator_consumer_supply ab8500_vaux2_consumers[] = { @@ -60,6 +120,12 @@ static struct regulator_consumer_supply ab8500_vaux2_consumers[] = { REGULATOR_SUPPLY("vmmc", "sdi4"), /* AB8500 audio codec */ REGULATOR_SUPPLY("vcc-N2158", "ab8500-codec.0"), + /* AB8500 accessory detect 1 */ + REGULATOR_SUPPLY("vcc-N2158", "ab8500-acc-det.0"), + /* AB8500 Tv-out device */ + REGULATOR_SUPPLY("vcc-N2158", "mcde_tv_ab8500.4"), + /* AV8100 HDMI device */ + REGULATOR_SUPPLY("vcc-N2158", "av8100_hdmi.3"), }; static struct regulator_consumer_supply ab8500_vaux3_consumers[] = { @@ -72,6 +138,30 @@ static struct regulator_consumer_supply ab8500_vtvout_consumers[] = { REGULATOR_SUPPLY("vtvout", "ab8500-denc.0"), /* Internal general-purpose ADC */ REGULATOR_SUPPLY("vddadc", "ab8500-gpadc.0"), + /* ADC for charger */ + REGULATOR_SUPPLY("vddadc", "ab8500-charger.0"), + /* AB8500 Tv-out device */ + REGULATOR_SUPPLY("vtvout", "mcde_tv_ab8500.4"), +}; + +static struct regulator_consumer_supply ab8500_vaudio_consumers[] = { + /* AB8500 audio codec device */ + REGULATOR_SUPPLY("v-audio", NULL), +}; + +static struct regulator_consumer_supply ab8500_vamic1_consumers[] = { + /* AB8500 audio codec device */ + REGULATOR_SUPPLY("v-amic1", NULL), +}; + +static struct regulator_consumer_supply ab8500_vamic2_consumers[] = { + /* AB8500 audio codec device */ + REGULATOR_SUPPLY("v-amic2", NULL), +}; + +static struct regulator_consumer_supply ab8500_vdmic_consumers[] = { + /* AB8500 audio codec device */ + REGULATOR_SUPPLY("v-dmic", NULL), }; static struct regulator_consumer_supply ab8500_vintcore_consumers[] = { @@ -82,74 +172,90 @@ static struct regulator_consumer_supply ab8500_vintcore_consumers[] = { }; static struct regulator_consumer_supply ab8500_vana_consumers[] = { - /* External displays, connector on board, 1v8 power supply */ - REGULATOR_SUPPLY("vsmps2", "mcde.0"), + /* DB8500 DSI */ + REGULATOR_SUPPLY("vdddsi1v2", "mcde"), + /* DB8500 CSI */ + REGULATOR_SUPPLY("vddcsi1v2", "mmio_camera"), +}; + +static struct regulator_consumer_supply ab8500_sysclkreq_2_consumers[] = { + /* CG2900 device */ + REGULATOR_SUPPLY("gbf_1v8", "cg2900-uart.0"), +}; + +static struct regulator_consumer_supply ab8500_sysclkreq_4_consumers[] = { + /* CW1200 device */ + REGULATOR_SUPPLY("wlan_1v8", "cw1200_wlan.0"), }; /* ab8500 regulator register initialization */ -struct ab8500_regulator_reg_init -ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { +static struct ab8500_regulator_reg_init ab8500_reg_init[] = { /* * VanaRequestCtrl = HP/LP depending on VxRequest + * VpllRequestCtrl = HP/LP depending on VxRequest * VextSupply1RequestCtrl = HP/LP depending on VxRequest */ - INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL2, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL2, 0xfc, 0x00), /* * VextSupply2RequestCtrl = HP/LP depending on VxRequest * VextSupply3RequestCtrl = HP/LP depending on VxRequest * Vaux1RequestCtrl = HP/LP depending on VxRequest * Vaux2RequestCtrl = HP/LP depending on VxRequest */ - INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL3, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL3, 0xff, 0x00), /* * Vaux3RequestCtrl = HP/LP depending on VxRequest * SwHPReq = Control through SWValid disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL4, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUREQUESTCTRL4, 0x07, 0x00), /* + * Vsmps1SysClkReq1HPValid = enabled + * Vsmps2SysClkReq1HPValid = enabled + * Vsmps3SysClkReq1HPValid = enabled * VanaSysClkReq1HPValid = disabled + * VpllSysClkReq1HPValid = enabled * Vaux1SysClkReq1HPValid = disabled * Vaux2SysClkReq1HPValid = disabled * Vaux3SysClkReq1HPValid = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID1, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID1, 0xff, 0x17), /* * VextSupply1SysClkReq1HPValid = disabled * VextSupply2SysClkReq1HPValid = disabled * VextSupply3SysClkReq1HPValid = SysClkReq1 controlled */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID2, 0x40), + INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQ1HPVALID2, 0x70, 0x40), /* * VanaHwHPReq1Valid = disabled * Vaux1HwHPreq1Valid = disabled * Vaux2HwHPReq1Valid = disabled * Vaux3HwHPReqValid = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID1, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID1, 0xe8, 0x00), /* * VextSupply1HwHPReq1Valid = disabled * VextSupply2HwHPReq1Valid = disabled * VextSupply3HwHPReq1Valid = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID2, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ1VALID2, 0x07, 0x00), /* * VanaHwHPReq2Valid = disabled * Vaux1HwHPReq2Valid = disabled * Vaux2HwHPReq2Valid = disabled * Vaux3HwHPReq2Valid = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID1, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID1, 0xe8, 0x00), /* * VextSupply1HwHPReq2Valid = disabled * VextSupply2HwHPReq2Valid = disabled * VextSupply3HwHPReq2Valid = HWReq2 controlled */ - INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID2, 0x04), + INIT_REGULATOR_REGISTER(AB8500_REGUHWHPREQ2VALID2, 0x07, 0x04), /* * VanaSwHPReqValid = disabled * Vaux1SwHPReqValid = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID1, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID1, 0xa0, 0x00), /* * Vaux2SwHPReqValid = disabled * Vaux3SwHPReqValid = disabled @@ -157,7 +263,7 @@ ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { * VextSupply2SwHPReqValid = disabled * VextSupply3SwHPReqValid = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID2, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUSWHPREQVALID2, 0x1f, 0x00), /* * SysClkReq2Valid1 = SysClkReq2 controlled * SysClkReq3Valid1 = disabled @@ -167,7 +273,7 @@ ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { * SysClkReq7Valid1 = disabled * SysClkReq8Valid1 = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID1, 0x2a), + INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID1, 0xfe, 0x2a), /* * SysClkReq2Valid2 = disabled * SysClkReq3Valid2 = disabled @@ -177,7 +283,7 @@ ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { * SysClkReq7Valid2 = disabled * SysClkReq8Valid2 = disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID2, 0x20), + INIT_REGULATOR_REGISTER(AB8500_REGUSYSCLKREQVALID2, 0xfe, 0x20), /* * VTVoutEna = disabled * Vintcore12Ena = disabled @@ -185,66 +291,93 @@ ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { * Vintcore12LP = inactive (HP) * VTVoutLP = inactive (HP) */ - INIT_REGULATOR_REGISTER(AB8500_REGUMISC1, 0x10), + INIT_REGULATOR_REGISTER(AB8500_REGUMISC1, 0xfe, 0x10), /* * VaudioEna = disabled * VdmicEna = disabled * Vamic1Ena = disabled * Vamic2Ena = disabled */ - INIT_REGULATOR_REGISTER(AB8500_VAUDIOSUPPLY, 0x00), + INIT_REGULATOR_REGISTER(AB8500_VAUDIOSUPPLY, 0x1e, 0x00), /* * Vamic1_dzout = high-Z when Vamic1 is disabled * Vamic2_dzout = high-Z when Vamic2 is disabled */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRL1VAMIC, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUCTRL1VAMIC, 0x03, 0x00), + /* + * Vsmps1Regu = HW control + * Vsmps1SelCtrl = Vsmps1 voltage defined by Vsmsp1Sel2 + */ + INIT_REGULATOR_REGISTER(AB8500_VSMPS1REGU, 0x0f, 0x06), + /* + * Vsmps2Regu = HW control + * Vsmps2SelCtrl = Vsmps2 voltage defined by Vsmsp2Sel2 + */ + INIT_REGULATOR_REGISTER(AB8500_VSMPS2REGU, 0x0f, 0x06), + /* + * Vsmps3Sel2 = 1.2125 V + * NOTE! PRCMU register + */ + INIT_REGULATOR_REGISTER(AB8500_VSMPS3SEL2, 0x7f, 0x29), + /* + * Vsmps3Regu = HW control + * Vsmps3SelCtrl = Vsmps3 voltage defined by Vsmps3Sel2 + * NOTE! PRCMU register + */ + INIT_REGULATOR_REGISTER(AB8500_VSMPS3REGU, 0x0f, 0x06), + /* + * Vsmps3Sel1 = 0.925V + * NOTE! PRCMU register + */ + INIT_REGULATOR_REGISTER(AB8500_VSMPS3SEL1, 0x7f, 0x12), /* * VPll = Hw controlled * VanaRegu = force off */ - INIT_REGULATOR_REGISTER(AB8500_VPLLVANAREGU, 0x02), + INIT_REGULATOR_REGISTER(AB8500_VPLLVANAREGU, 0x0f, 0x02), /* * VrefDDREna = disabled * VrefDDRSleepMode = inactive (no pulldown) */ - INIT_REGULATOR_REGISTER(AB8500_VREFDDR, 0x00), + INIT_REGULATOR_REGISTER(AB8500_VREFDDR, 0x03, 0x00), /* * VextSupply1Regu = HW control * VextSupply2Regu = HW control - * VextSupply3Regu = HW control + * VextSupply3Regu = Low Power mode * ExtSupply2Bypass = ExtSupply12LPn ball is 0 when Ena is 0 * ExtSupply3Bypass = ExtSupply3LPn ball is 0 when Ena is 0 */ - INIT_REGULATOR_REGISTER(AB8500_EXTSUPPLYREGU, 0x2a), + INIT_REGULATOR_REGISTER(AB8500_EXTSUPPLYREGU, 0xff, 0x1a), /* * Vaux1Regu = force HP * Vaux2Regu = force off */ - INIT_REGULATOR_REGISTER(AB8500_VAUX12REGU, 0x01), + INIT_REGULATOR_REGISTER(AB8500_VAUX12REGU, 0x0f, 0x01), /* - * Vaux3regu = force off + * Vrf1Regu = HW control + * Vaux3Regu = force off */ - INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3REGU, 0x00), + INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3REGU, 0x0f, 0x08), /* - * Vsmps1 = 1.15V + * Vsmps1Sel1 = 1.2 V */ - INIT_REGULATOR_REGISTER(AB8500_VSMPS1SEL1, 0x24), + INIT_REGULATOR_REGISTER(AB8500_VSMPS1SEL1, 0x3f, 0x28), /* - * Vaux1Sel = 2.5 V + * Vaux1Sel = 2.8 V */ - INIT_REGULATOR_REGISTER(AB8500_VAUX1SEL, 0x08), + INIT_REGULATOR_REGISTER(AB8500_VAUX1SEL, 0x0f, 0x0C), /* * Vaux2Sel = 2.9 V */ - INIT_REGULATOR_REGISTER(AB8500_VAUX2SEL, 0x0d), + INIT_REGULATOR_REGISTER(AB8500_VAUX2SEL, 0x0f, 0x0d), /* * Vaux3Sel = 2.91 V */ - INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3SEL, 0x07), + INIT_REGULATOR_REGISTER(AB8500_VRF1VAUX3SEL, 0x07, 0x07), /* * VextSupply12LP = disabled (no LP) */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRL2SPARE, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUCTRL2SPARE, 0x01, 0x00), /* * Vaux1Disch = short discharge time * Vaux2Disch = short discharge time @@ -253,23 +386,24 @@ ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS] = { * VTVoutDisch = short discharge time * VaudioDisch = short discharge time */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH, 0xfc, 0x00), /* * VanaDisch = short discharge time * VdmicPullDownEna = pulldown disabled when Vdmic is disabled * VdmicDisch = short discharge time */ - INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH2, 0x00), + INIT_REGULATOR_REGISTER(AB8500_REGUCTRLDISCH2, 0x16, 0x00), }; /* AB8500 regulators */ -struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { +static struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { /* supplies to the display/camera */ [AB8500_LDO_AUX1] = { + .supply_regulator = "ab8500-ext-supply3", .constraints = { .name = "V-DISPLAY", - .min_uV = 2500000, - .max_uV = 2900000, + .min_uV = 2800000, + .max_uV = 3300000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS, .boot_on = 1, /* display is on at boot */ @@ -286,24 +420,32 @@ struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { }, /* supplies to the on-board eMMC */ [AB8500_LDO_AUX2] = { + .supply_regulator = "ab8500-ext-supply3", .constraints = { .name = "V-eMMC1", .min_uV = 1100000, .max_uV = 3300000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | - REGULATOR_CHANGE_STATUS, + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, }, .num_consumer_supplies = ARRAY_SIZE(ab8500_vaux2_consumers), .consumer_supplies = ab8500_vaux2_consumers, }, /* supply for VAUX3, supplies to SDcard slots */ [AB8500_LDO_AUX3] = { + .supply_regulator = "ab8500-ext-supply3", .constraints = { .name = "V-MMC-SD", .min_uV = 1100000, .max_uV = 3300000, .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | - REGULATOR_CHANGE_STATUS, + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, }, .num_consumer_supplies = ARRAY_SIZE(ab8500_vaux3_consumers), .consumer_supplies = ab8500_vaux3_consumers, @@ -323,6 +465,8 @@ struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { .name = "V-AUD", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vaudio_consumers), + .consumer_supplies = ab8500_vaudio_consumers, }, /* supply for v-anamic1 VAMic1-LDO */ [AB8500_LDO_ANAMIC1] = { @@ -330,6 +474,8 @@ struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { .name = "V-AMIC1", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vamic1_consumers), + .consumer_supplies = ab8500_vamic1_consumers, }, /* supply for v-amic2, VAMIC2 LDO, reuse constants for AMIC1 */ [AB8500_LDO_ANAMIC2] = { @@ -337,6 +483,8 @@ struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { .name = "V-AMIC2", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vamic2_consumers), + .consumer_supplies = ab8500_vamic2_consumers, }, /* supply for v-dmic, VDMIC LDO */ [AB8500_LDO_DMIC] = { @@ -344,23 +492,87 @@ struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS] = { .name = "V-DMIC", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, + .num_consumer_supplies = ARRAY_SIZE(ab8500_vdmic_consumers), + .consumer_supplies = ab8500_vdmic_consumers, }, /* supply for v-intcore12, VINTCORE12 LDO */ [AB8500_LDO_INTCORE] = { .constraints = { .name = "V-INTCORE", - .valid_ops_mask = REGULATOR_CHANGE_STATUS, + .min_uV = 1250000, + .max_uV = 1350000, + .input_uV = 1800000, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE | + REGULATOR_CHANGE_DRMS, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, }, .num_consumer_supplies = ARRAY_SIZE(ab8500_vintcore_consumers), .consumer_supplies = ab8500_vintcore_consumers, }, - /* supply for U8500 CSI/DSI, VANA LDO */ + /* supply for U8500 CSI-DSI, VANA LDO */ [AB8500_LDO_ANA] = { .constraints = { - .name = "V-CSI/DSI", + .name = "V-CSI-DSI", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, .num_consumer_supplies = ARRAY_SIZE(ab8500_vana_consumers), .consumer_supplies = ab8500_vana_consumers, }, + /* sysclkreq 2 pin */ + [AB8500_SYSCLKREQ_2] = { + .constraints = { + .name = "V-SYSCLKREQ-2", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = + ARRAY_SIZE(ab8500_sysclkreq_2_consumers), + .consumer_supplies = ab8500_sysclkreq_2_consumers, + }, + /* sysclkreq 4 pin */ + [AB8500_SYSCLKREQ_4] = { + .constraints = { + .name = "V-SYSCLKREQ-4", + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = + ARRAY_SIZE(ab8500_sysclkreq_4_consumers), + .consumer_supplies = ab8500_sysclkreq_4_consumers, + }, +}; + +/* supply for VextSupply3 */ +static struct regulator_consumer_supply ab8500_ext_supply3_consumers[] = { + /* SIM supply for 3 V SIM cards */ + REGULATOR_SUPPLY("vinvsim", "sim-detect.0"), +}; + +/* + * AB8500 external regulators + */ +static struct regulator_init_data ab8500_ext_regulators[] = { + /* fixed Vbat supplies VSMPS3_EXT_3V4 and VSMPS4_EXT_3V4 */ + [AB8500_EXT_SUPPLY3] = { + .constraints = { + .name = "ab8500-ext-supply3", + .min_uV = 3400000, + .max_uV = 3400000, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + .boot_on = 1, + }, + .num_consumer_supplies = + ARRAY_SIZE(ab8500_ext_supply3_consumers), + .consumer_supplies = ab8500_ext_supply3_consumers, + }, +}; + +struct ab8500_regulator_platform_data ab8500_regulator_plat_data = { + .reg_init = ab8500_reg_init, + .num_reg_init = ARRAY_SIZE(ab8500_reg_init), + .regulator = ab8500_regulators, + .num_regulator = ARRAY_SIZE(ab8500_regulators), + .ext_regulator = ab8500_ext_regulators, + .num_ext_regulator = ARRAY_SIZE(ab8500_ext_regulators), }; diff --git a/arch/arm/mach-ux500/board-mop500-regulators.h b/arch/arm/mach-ux500/board-mop500-regulators.h index 94992158d96..ed309081e14 100644 --- a/arch/arm/mach-ux500/board-mop500-regulators.h +++ b/arch/arm/mach-ux500/board-mop500-regulators.h @@ -14,9 +14,9 @@ #include <linux/regulator/machine.h> #include <linux/regulator/ab8500.h> -extern struct ab8500_regulator_reg_init -ab8500_regulator_reg_init[AB8500_NUM_REGULATOR_REGISTERS]; -extern struct regulator_init_data ab8500_regulators[AB8500_NUM_REGULATORS]; +extern struct ab8500_regulator_platform_data ab8500_regulator_plat_data; extern struct regulator_init_data tps61052_regulator; +extern struct regulator_init_data gpio_wlan_vbat_regulator; +extern struct regulator_init_data gpio_en_3v3_regulator; #endif diff --git a/arch/arm/mach-ux500/board-mop500-sdi.c b/arch/arm/mach-ux500/board-mop500-sdi.c index d0cb9e5eb87..ee49e5f7f33 100644 --- a/arch/arm/mach-ux500/board-mop500-sdi.c +++ b/arch/arm/mach-ux500/board-mop500-sdi.c @@ -11,35 +11,28 @@ #include <linux/amba/mmci.h> #include <linux/mmc/host.h> #include <linux/platform_device.h> +#include <linux/delay.h> #include <asm/mach-types.h> #include <plat/ste_dma40.h> #include <mach/devices.h> #include <mach/hardware.h> +#include <mach/ste-dma40-db8500.h> #include "devices-db8500.h" #include "board-mop500.h" -#include "ste-dma40-db8500.h" /* * SDI 0 (MicroSD slot) */ -/* MMCIPOWER bits */ -#define MCI_DATA2DIREN (1 << 2) -#define MCI_CMDDIREN (1 << 3) -#define MCI_DATA0DIREN (1 << 4) -#define MCI_DATA31DIREN (1 << 5) -#define MCI_FBCLKEN (1 << 7) - /* GPIO pins used by the sdi0 level shifter */ static int sdi0_en = -1; static int sdi0_vsel = -1; -static u32 mop500_sdi0_vdd_handler(struct device *dev, unsigned int vdd, - unsigned char power_mode) +static int mop500_sdi0_ios_handler(struct device *dev, struct mmc_ios *ios) { - switch (power_mode) { + switch (ios->power_mode) { case MMC_POWER_UP: case MMC_POWER_ON: /* @@ -52,6 +45,7 @@ static u32 mop500_sdi0_vdd_handler(struct device *dev, unsigned int vdd, */ gpio_direction_output(sdi0_vsel, 0); gpio_direction_output(sdi0_en, 1); + udelay(100); break; case MMC_POWER_OFF: gpio_direction_output(sdi0_vsel, 0); @@ -59,68 +53,122 @@ static u32 mop500_sdi0_vdd_handler(struct device *dev, unsigned int vdd, break; } - return MCI_FBCLKEN | MCI_CMDDIREN | MCI_DATA0DIREN | - MCI_DATA2DIREN | MCI_DATA31DIREN; + return 0; } +#ifdef MMC_SUSPEND_WORKAROUND #ifdef CONFIG_STE_DMA40 struct stedma40_chan_cfg mop500_sdi0_dma_cfg_rx = { .mode = STEDMA40_MODE_LOGICAL, .dir = STEDMA40_PERIPH_TO_MEM, - .src_dev_type = DB8500_DMA_DEV29_SD_MM0_RX, + .src_dev_type = DB8500_DMA_DEV1_SD_MMC0_RX, .dst_dev_type = STEDMA40_DEV_DST_MEMORY, .src_info.data_width = STEDMA40_WORD_WIDTH, .dst_info.data_width = STEDMA40_WORD_WIDTH, + .use_fixed_channel = true, + .phy_channel = 0, }; static struct stedma40_chan_cfg mop500_sdi0_dma_cfg_tx = { .mode = STEDMA40_MODE_LOGICAL, .dir = STEDMA40_MEM_TO_PERIPH, .src_dev_type = STEDMA40_DEV_SRC_MEMORY, - .dst_dev_type = DB8500_DMA_DEV29_SD_MM0_TX, + .dst_dev_type = DB8500_DMA_DEV1_SD_MMC0_TX, .src_info.data_width = STEDMA40_WORD_WIDTH, .dst_info.data_width = STEDMA40_WORD_WIDTH, + .use_fixed_channel = true, + .phy_channel = 0, }; #endif +#endif static struct mmci_platform_data mop500_sdi0_data = { - .vdd_handler = mop500_sdi0_vdd_handler, + .ios_handler = mop500_sdi0_ios_handler, .ocr_mask = MMC_VDD_29_30, .f_max = 50000000, .capabilities = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED, .gpio_wp = -1, + .sigdir = MCI_ST_FBCLKEN | + MCI_ST_CMDDIREN | + MCI_ST_DATA0DIREN | + MCI_ST_DATA2DIREN, +#ifdef MMC_SUSPEND_WORKAROUND #ifdef CONFIG_STE_DMA40 .dma_filter = stedma40_filter, .dma_rx_param = &mop500_sdi0_dma_cfg_rx, .dma_tx_param = &mop500_sdi0_dma_cfg_tx, #endif +#endif +}; + +/* + * SDI1 (SDIO WLAN) + */ +#ifdef SDIO_DMA_ON +#ifdef CONFIG_STE_DMA40 +static struct stedma40_chan_cfg sdi1_dma_cfg_rx = { + .mode = STEDMA40_MODE_LOGICAL, + .dir = STEDMA40_PERIPH_TO_MEM, + .src_dev_type = DB8500_DMA_DEV32_SD_MM1_RX, + .dst_dev_type = STEDMA40_DEV_DST_MEMORY, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, }; -static void sdi0_configure(void) +static struct stedma40_chan_cfg sdi1_dma_cfg_tx = { + .mode = STEDMA40_MODE_LOGICAL, + .dir = STEDMA40_MEM_TO_PERIPH, + .src_dev_type = STEDMA40_DEV_SRC_MEMORY, + .dst_dev_type = DB8500_DMA_DEV32_SD_MM1_TX, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, +}; +#endif +#endif + +static struct mmci_platform_data mop500_sdi1_data = { + .ocr_mask = MMC_VDD_29_30, + .f_max = 50000000, + .capabilities = MMC_CAP_4_BIT_DATA, + .gpio_cd = -1, + .gpio_wp = -1, +#ifdef SDIO_DMA_ON +#ifdef CONFIG_STE_DMA40 + .dma_filter = stedma40_filter, + .dma_rx_param = &sdi1_dma_cfg_rx, + .dma_tx_param = &sdi1_dma_cfg_tx, +#endif +#endif +}; + +static void sdi0_sdi1_configure(void) { - int ret; + int ret; + u32 periphid = 0; - ret = gpio_request(sdi0_en, "level shifter enable"); - if (!ret) - ret = gpio_request(sdi0_vsel, - "level shifter 1v8-3v select"); - if (ret) { - pr_warning("unable to config sdi0 gpios for level shifter.\n"); - return; - } + ret = gpio_request(sdi0_en, "level shifter enable"); + if (!ret) + ret = gpio_request(sdi0_vsel, + "level shifter 1v8-3v select"); - /* Select the default 2.9V and enable level shifter */ - gpio_direction_output(sdi0_vsel, 0); - gpio_direction_output(sdi0_en, 1); + if (ret) { + pr_warning("unable to config sdi0 gpios for level shifter.\n"); + return; + } + + /* Select the default 2.9V and enable level shifter */ + gpio_direction_output(sdi0_vsel, 0); + gpio_direction_output(sdi0_en, 1); + + /* v2 has a new version of this block that need to be forced */ + if (cpu_is_u8500v20_or_later()) + periphid = 0x10480180; - /* Add the device, force v2 to subrevision 1 */ - if (cpu_is_u8500v2()) - db8500_add_sdi0(&mop500_sdi0_data, 0x10480180); - else - db8500_add_sdi0(&mop500_sdi0_data, 0); + db8500_add_sdi0(&mop500_sdi0_data, periphid); + db8500_add_sdi1(&mop500_sdi1_data, periphid); } void mop500_sdi_tc35892_init(void) @@ -128,13 +176,13 @@ void mop500_sdi_tc35892_init(void) mop500_sdi0_data.gpio_cd = GPIO_SDMMC_CD; sdi0_en = GPIO_SDMMC_EN; sdi0_vsel = GPIO_SDMMC_1V8_3V_SEL; - sdi0_configure(); + sdi0_sdi1_configure(); } /* * SDI 2 (POP eMMC, not on DB8500ed) */ - +#ifdef MMC_SUSPEND_WORKAROUND #ifdef CONFIG_STE_DMA40 struct stedma40_chan_cfg mop500_sdi2_dma_cfg_rx = { .mode = STEDMA40_MODE_LOGICAL, @@ -154,24 +202,29 @@ static struct stedma40_chan_cfg mop500_sdi2_dma_cfg_tx = { .dst_info.data_width = STEDMA40_WORD_WIDTH, }; #endif +#endif static struct mmci_platform_data mop500_sdi2_data = { .ocr_mask = MMC_VDD_165_195, .f_max = 50000000, - .capabilities = MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA, + .capabilities = MMC_CAP_4_BIT_DATA | + MMC_CAP_8_BIT_DATA, .gpio_cd = -1, .gpio_wp = -1, +#ifdef MMC_SUSPEND_WORKAROUND #ifdef CONFIG_STE_DMA40 .dma_filter = stedma40_filter, .dma_rx_param = &mop500_sdi2_dma_cfg_rx, .dma_tx_param = &mop500_sdi2_dma_cfg_tx, #endif +#endif }; /* * SDI 4 (on-board eMMC) */ +#ifdef MMC_SUSPEND_WORKAROUND #ifdef CONFIG_STE_DMA40 struct stedma40_chan_cfg mop500_sdi4_dma_cfg_rx = { .mode = STEDMA40_MODE_LOGICAL, @@ -191,19 +244,23 @@ static struct stedma40_chan_cfg mop500_sdi4_dma_cfg_tx = { .dst_info.data_width = STEDMA40_WORD_WIDTH, }; #endif +#endif static struct mmci_platform_data mop500_sdi4_data = { .ocr_mask = MMC_VDD_29_30, .f_max = 50000000, - .capabilities = MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA | - MMC_CAP_MMC_HIGHSPEED, + .capabilities = MMC_CAP_4_BIT_DATA | + MMC_CAP_8_BIT_DATA | + MMC_CAP_MMC_HIGHSPEED, .gpio_cd = -1, .gpio_wp = -1, +#ifdef MMC_SUSPEND_WORKAROUND #ifdef CONFIG_STE_DMA40 .dma_filter = stedma40_filter, .dma_rx_param = &mop500_sdi4_dma_cfg_rx, .dma_tx_param = &mop500_sdi4_dma_cfg_tx, #endif +#endif }; void __init mop500_sdi_init(void) @@ -211,11 +268,13 @@ void __init mop500_sdi_init(void) u32 periphid = 0; /* v2 has a new version of this block that need to be forced */ - if (cpu_is_u8500v2()) + if (cpu_is_u8500v20_or_later()) { periphid = 0x10480180; - /* PoP:ed eMMC on top of DB8500 v1.0 has problems with high speed */ - if (!cpu_is_u8500v10()) + + /* PoP:ed eMMC on DB8500 v1.0 has problems with high speed */ mop500_sdi2_data.capabilities |= MMC_CAP_MMC_HIGHSPEED; + } + /* sdi2 on snowball is in ATL_B mode for FSMC (LAN) */ if (!machine_is_snowball()) db8500_add_sdi2(&mop500_sdi2_data, periphid); @@ -234,11 +293,11 @@ void __init mop500_sdi_init(void) sdi0_en = SNOWBALL_SDMMC_EN_GPIO; sdi0_vsel = SNOWBALL_SDMMC_1V8_3V_GPIO; } - sdi0_configure(); + sdi0_sdi1_configure(); } /* - * On boards with the TC35892 GPIO expander, sdi0 will finally + * On boards with the TC35892 GPIO expander, sdi0 and sdi1 will finally * be added when the TC35892 initializes and calls * mop500_sdi_tc35892_init() above. */ diff --git a/arch/arm/mach-ux500/board-mop500-stuib.c b/arch/arm/mach-ux500/board-mop500-stuib.c index 8c979770d87..273869bf3ca 100644 --- a/arch/arm/mach-ux500/board-mop500-stuib.c +++ b/arch/arm/mach-ux500/board-mop500-stuib.c @@ -11,33 +11,70 @@ #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/i2c.h> +#ifdef CONFIG_U8500_FLASH +#include <../drivers/staging/camera_flash/adp1653_plat.h> +#endif #include <linux/input/matrix_keypad.h> #include <asm/mach-types.h> #include "board-mop500.h" -/* STMPE/SKE keypad use this key layout */ +/* + * ux500 keymaps + * + * Organized row-wise as on the UIB, starting at the top-left + * + * we support two key layouts, specific to requirements. The first + * keylayout includes controls for power/volume a few generic keys; + * the second key layout contains the full numeric layout, enter/back/left + * buttons along with a "."(dot), specifically for connectivity testing + */ static const unsigned int mop500_keymap[] = { +#if defined(CONFIG_KEYLAYOUT_LAYOUT1) KEY(2, 5, KEY_END), - KEY(4, 1, KEY_POWER), + KEY(4, 1, KEY_HOME), KEY(3, 5, KEY_VOLUMEDOWN), - KEY(1, 3, KEY_3), + KEY(1, 3, KEY_EMAIL), KEY(5, 2, KEY_RIGHT), - KEY(5, 0, KEY_9), + KEY(5, 0, KEY_BACKSPACE), KEY(0, 5, KEY_MENU), KEY(7, 6, KEY_ENTER), KEY(4, 5, KEY_0), - KEY(6, 7, KEY_2), + KEY(6, 7, KEY_DOT), KEY(3, 4, KEY_UP), KEY(3, 3, KEY_DOWN), KEY(6, 4, KEY_SEND), KEY(6, 2, KEY_BACK), KEY(4, 2, KEY_VOLUMEUP), - KEY(5, 5, KEY_1), + KEY(5, 5, KEY_SPACE), KEY(4, 3, KEY_LEFT), + KEY(3, 2, KEY_SEARCH), +#elif defined(CONFIG_KEYLAYOUT_LAYOUT2) + KEY(2, 5, KEY_RIGHT), + KEY(4, 1, KEY_ENTER), + KEY(3, 5, KEY_MENU), + KEY(1, 3, KEY_3), + KEY(5, 2, KEY_6), + KEY(5, 0, KEY_9), + + KEY(0, 5, KEY_UP), + KEY(7, 6, KEY_DOWN), + KEY(4, 5, KEY_0), + KEY(6, 7, KEY_2), + KEY(3, 4, KEY_5), + KEY(3, 3, KEY_8), + + KEY(6, 4, KEY_LEFT), + KEY(6, 2, KEY_BACK), + KEY(4, 2, KEY_KPDOT), + KEY(5, 5, KEY_1), + KEY(4, 3, KEY_4), KEY(3, 2, KEY_7), +#else +#warning "No keypad layout defined." +#endif }; static const struct matrix_keymap_data mop500_keymap_data = { @@ -73,6 +110,24 @@ static struct i2c_board_info __initdata mop500_i2c0_devices_stuib[] = { }, }; +#ifdef CONFIG_U8500_FLASH +/* + * Config data for the flash + */ +static struct adp1653_platform_data __initdata adp1653_pdata_u8500_uib = { + .irq_no = CAMERA_FLASH_INT_PIN +}; +#endif + +static struct i2c_board_info __initdata mop500_i2c2_devices_stuib[] = { +#ifdef CONFIG_U8500_FLASH + { + I2C_BOARD_INFO("adp1653", 0x30), + .platform_data = &adp1653_pdata_u8500_uib + } +#endif +}; + /* * BU21013 ROHM touchscreen interface on the STUIBs */ @@ -111,6 +166,7 @@ static int bu21013_gpio_board_init(int reset_pin) __func__); return retval; } + gpio_set_value_cansleep(reset_pin, 1); } return retval; @@ -133,7 +189,8 @@ static int bu21013_gpio_board_exit(int reset_pin) __func__); return retval; } - gpio_set_value(reset_pin, 0); + gpio_set_value_cansleep(reset_pin, 0); + gpio_free(reset_pin); } bu21013_devices--; @@ -176,11 +233,11 @@ static struct bu21013_platform_device tsc_plat2_device = { static struct i2c_board_info __initdata u8500_i2c3_devices_stuib[] = { { - I2C_BOARD_INFO("bu21013_tp", 0x5C), + I2C_BOARD_INFO("bu21013_ts", 0x5C), .platform_data = &tsc_plat_device, }, { - I2C_BOARD_INFO("bu21013_tp", 0x5D), + I2C_BOARD_INFO("bu21013_ts", 0x5D), .platform_data = &tsc_plat2_device, }, @@ -191,15 +248,25 @@ void __init mop500_stuib_init(void) if (machine_is_hrefv60()) { tsc_plat_device.cs_pin = HREFV60_TOUCH_RST_GPIO; tsc_plat2_device.cs_pin = HREFV60_TOUCH_RST_GPIO; +#ifdef CONFIG_U8500_FLASH + adp1653_pdata_u8500_uib.enable_gpio = + HREFV60_CAMERA_FLASH_ENABLE; +#endif } else { tsc_plat_device.cs_pin = GPIO_BU21013_CS; tsc_plat2_device.cs_pin = GPIO_BU21013_CS; - +#ifdef CONFIG_U8500_FLASH + adp1653_pdata_u8500_uib.enable_gpio = + GPIO_CAMERA_FLASH_ENABLE; +#endif } mop500_uib_i2c_add(0, mop500_i2c0_devices_stuib, ARRAY_SIZE(mop500_i2c0_devices_stuib)); + mop500_uib_i2c_add(2, mop500_i2c2_devices_stuib, + ARRAY_SIZE(mop500_i2c2_devices_stuib)); + mop500_uib_i2c_add(3, u8500_i2c3_devices_stuib, ARRAY_SIZE(u8500_i2c3_devices_stuib)); } diff --git a/arch/arm/mach-ux500/board-mop500-u8500uib.c b/arch/arm/mach-ux500/board-mop500-u8500uib.c index 8ce46c0fdfd..41eea4a79c8 100644 --- a/arch/arm/mach-ux500/board-mop500-u8500uib.c +++ b/arch/arm/mach-ux500/board-mop500-u8500uib.c @@ -8,12 +8,22 @@ #include <linux/kernel.h> #include <linux/init.h> #include <linux/i2c.h> +#ifdef CONFIG_U8500_FLASH +#include <../drivers/staging/camera_flash/adp1653_plat.h> +#endif #include <linux/gpio.h> #include <linux/interrupt.h> +#ifdef CONFIG_SENSORS_LSM303DLH +#include <linux/lsm303dlh.h> +#endif +#ifdef CONFIG_SENSORS_L3G4200D +#include <linux/l3g4200d.h> +#endif #include <linux/mfd/tc3589x.h> #include <linux/input/matrix_keypad.h> -#include <mach/gpio.h> +#include <asm/mach-types.h> +#include <linux/gpio.h> #include <mach/irqs.h> #include "board-mop500.h" @@ -22,12 +32,75 @@ struct i2c_board_info __initdata __weak mop500_i2c3_devices_u8500[] = { }; +#ifdef CONFIG_SENSORS_LSM303DLH +/* + * LSM303DLH accelerometer + magnetometer & L3G4200D Gyroscope sensors + */ +static struct lsm303dlh_platform_data __initdata lsm303dlh_pdata_u8500 = { + .name_a = "lsm303dlh.0", + .name_m = "lsm303dlh.1", + .axis_map_x = 1, + .axis_map_y = 0, + .axis_map_z = 2, + .negative_x = 1, + .negative_y = 1, + .negative_z = 1, +}; +#endif + +#ifdef CONFIG_SENSORS_L3G4200D +static struct l3g4200d_gyr_platform_data __initdata l3g4200d_pdata_u8500 = { + .name_gyr = "l3g4200d", + .axis_map_x = 1, + .axis_map_y = 0, + .axis_map_z = 2, + .negative_x = 0, + .negative_y = 0, + .negative_z = 1, +}; +#endif + +#ifdef CONFIG_U8500_FLASH +static struct adp1653_platform_data __initdata adp1653_pdata_u8500_uib = { + .irq_no = CAMERA_FLASH_INT_PIN +}; +#endif + +static struct i2c_board_info __initdata mop500_i2c2_devices_u8500[] = { +#ifdef CONFIG_SENSORS_LSM303DLH + { + /* LSM303DLH Accelerometer */ + I2C_BOARD_INFO("lsm303dlh_a", 0x18), + .platform_data = &lsm303dlh_pdata_u8500, + }, + { + /* LSM303DLH Magnetometer */ + I2C_BOARD_INFO("lsm303dlh_m", 0x1E), + .platform_data = &lsm303dlh_pdata_u8500, + }, +#endif +#ifdef CONFIG_SENSORS_L3G4200D + { + /* L3G4200D Gyroscope */ + I2C_BOARD_INFO("l3g4200d", 0x68), + .platform_data = &l3g4200d_pdata_u8500, + }, +#endif +#ifdef CONFIG_U8500_FLASH + { + I2C_BOARD_INFO("adp1653", 0x30), + .platform_data = &adp1653_pdata_u8500_uib + } +#endif +}; + + /* * TC35893 */ static const unsigned int u8500_keymap[] = { KEY(3, 1, KEY_END), - KEY(4, 1, KEY_POWER), + KEY(4, 1, KEY_HOME), KEY(6, 4, KEY_VOLUMEDOWN), KEY(4, 2, KEY_EMAIL), KEY(3, 3, KEY_RIGHT), @@ -88,4 +161,28 @@ void __init mop500_u8500uib_init(void) mop500_uib_i2c_add(0, mop500_i2c0_devices_u8500, ARRAY_SIZE(mop500_i2c0_devices_u8500)); + if (machine_is_hrefv60()) { +#ifdef CONFIG_SENSORS_LSM303DLH + lsm303dlh_pdata_u8500.irq_a1 = HREFV60_ACCEL_INT1_GPIO; + lsm303dlh_pdata_u8500.irq_a2 = HREFV60_ACCEL_INT2_GPIO; + lsm303dlh_pdata_u8500.irq_m = HREFV60_MAGNET_DRDY_GPIO; +#endif +#ifdef CONFIG_U8500_FLASH + adp1653_pdata_u8500_uib.enable_gpio = + HREFV60_CAMERA_FLASH_ENABLE; +#endif + } else { +#ifdef CONFIG_SENSORS_LSM303DLH + lsm303dlh_pdata_u8500.irq_a1 = GPIO_ACCEL_INT1; + lsm303dlh_pdata_u8500.irq_a2 = GPIO_ACCEL_INT2; + lsm303dlh_pdata_u8500.irq_m = GPIO_MAGNET_DRDY; +#endif +#ifdef CONFIG_U8500_FLASH + adp1653_pdata_u8500_uib.enable_gpio = + GPIO_CAMERA_FLASH_ENABLE; +#endif + } + + mop500_uib_i2c_add(2, mop500_i2c2_devices_u8500, + ARRAY_SIZE(mop500_i2c2_devices_u8500)); } diff --git a/arch/arm/mach-ux500/board-mop500-uib.c b/arch/arm/mach-ux500/board-mop500-uib.c index 5af36aa56c0..8679b15643c 100644 --- a/arch/arm/mach-ux500/board-mop500-uib.c +++ b/arch/arm/mach-ux500/board-mop500-uib.c @@ -1,4 +1,5 @@ /* + * Copyright (C) ST-Ericsson SA 2010 * * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson @@ -10,13 +11,16 @@ #include <linux/kernel.h> #include <linux/init.h> #include <linux/i2c.h> - #include <mach/hardware.h> +#include <asm/mach-types.h> + #include "board-mop500.h" enum mop500_uib { STUIB, U8500UIB, + U8500UIB_R3, + NO_UIB, }; struct uib { @@ -25,6 +29,8 @@ struct uib { void (*init)(void); }; +static u8 type_of_uib = NO_UIB; + static struct uib __initdata mop500_uibs[] = { [STUIB] = { .name = "ST-UIB", @@ -36,9 +42,16 @@ static struct uib __initdata mop500_uibs[] = { .option = "u8500uib", .init = mop500_u8500uib_init, }, +#ifdef CONFIG_TOUCHSCREEN_CYTTSP_SPI + [U8500UIB_R3] = { + .name = "U8500-UIBR3", + .option = "u8500uibr3", + .init = mop500_u8500uib_r3_init, + }, +#endif }; -static struct uib *mop500_uib; +static struct uib __initdata *mop500_uib; static int __init mop500_uib_setup(char *str) { @@ -64,7 +77,7 @@ __setup("uib=", mop500_uib_setup); * The UIBs are detected after the I2C host controllers are registered, so * i2c_register_board_info() can't be used. */ -void mop500_uib_i2c_add(int busnum, struct i2c_board_info *info, +void mop500_uib_i2c_add(int busnum, struct i2c_board_info const *info, unsigned n) { struct i2c_adapter *adap; @@ -90,26 +103,46 @@ void mop500_uib_i2c_add(int busnum, struct i2c_board_info *info, static void __init __mop500_uib_init(struct uib *uib, const char *why) { pr_info("%s (%s)\n", uib->name, why); + + if (strcmp("stuib", uib->option) == 0) + type_of_uib = STUIB; + else if (strcmp("u8500uib", uib->option) == 0) + type_of_uib = U8500UIB; + else if (strcmp("u8500uibr3", uib->option) == 0) + type_of_uib = U8500UIB_R3; + uib->init(); } +int uib_is_stuib(void) +{ + return (type_of_uib == STUIB); +} + +int uib_is_u8500uib(void) +{ + return (type_of_uib == U8500UIB); +} + +int uib_is_u8500uibr3(void) +{ + return (type_of_uib == U8500UIB_R3); +} + /* * Detect the UIB attached based on the presence or absence of i2c devices. */ static int __init mop500_uib_init(void) { - struct uib *uib = mop500_uib; + struct uib *uib = mop500_uibs; struct i2c_adapter *i2c0; + struct i2c_adapter *i2c3; int ret; - if (!cpu_is_u8500()) + /* snowball and non u8500 cpus dont have uib */ + if (!cpu_is_u8500() || machine_is_snowball()) return -ENODEV; - if (uib) { - __mop500_uib_init(uib, "from uib= boot argument"); - return 0; - } - i2c0 = i2c_get_adapter(0); if (!i2c0) { __mop500_uib_init(&mop500_uibs[STUIB], @@ -121,12 +154,28 @@ static int __init mop500_uib_init(void) ret = i2c_smbus_xfer(i2c0, 0x44, 0, I2C_SMBUS_WRITE, 0, I2C_SMBUS_QUICK, NULL); i2c_put_adapter(i2c0); - - if (ret == 0) - uib = &mop500_uibs[U8500UIB]; - else - uib = &mop500_uibs[STUIB]; - + i2c3 = i2c_get_adapter(3); + if (ret == 0) { + if (!i2c3) { + __mop500_uib_init(&mop500_uibs[STUIB], + "fallback, could not get i2c3"); + return -ENODEV; + } + ret = i2c_smbus_xfer(i2c3, 0x4B, 0, I2C_SMBUS_WRITE, 0, + I2C_SMBUS_QUICK, NULL); + i2c_put_adapter(i2c3); + if (ret == 0) + uib = &mop500_uibs[U8500UIB]; + else + uib = &mop500_uibs[U8500UIB_R3]; + } + else { + ret = i2c_smbus_xfer(i2c3, 0x5C, 0, I2C_SMBUS_WRITE, 0, + I2C_SMBUS_QUICK, NULL); + i2c_put_adapter(i2c3); + if (ret == 0) + uib = &mop500_uibs[STUIB]; + } __mop500_uib_init(uib, "detected"); return 0; diff --git a/arch/arm/mach-ux500/board-mop500.c b/arch/arm/mach-ux500/board-mop500.c index cd54abaccd9..7d2eea57d9e 100644 --- a/arch/arm/mach-ux500/board-mop500.c +++ b/arch/arm/mach-ux500/board-mop500.c @@ -19,18 +19,28 @@ #include <linux/amba/pl022.h> #include <linux/amba/serial.h> #include <linux/spi/spi.h> +#ifdef CONFIG_HSI +#include <linux/hsi/hsi.h> +#endif #include <linux/mfd/ab8500.h> #include <linux/regulator/ab8500.h> #include <linux/mfd/tc3589x.h> -#include <linux/mfd/tps6105x.h> #include <linux/mfd/ab8500/gpio.h> +#include <linux/regulator/fixed.h> #include <linux/leds-lp5521.h> #include <linux/input.h> #include <linux/smsc911x.h> #include <linux/gpio_keys.h> #include <linux/delay.h> - +#include <linux/mfd/ab8500/denc.h> +#ifdef CONFIG_STM_MSP_I2S +#include <linux/spi/stm_msp.h> +#endif +#include <linux/leds_pwm.h> +#include <linux/pwm_backlight.h> +#include <linux/gpio/nomadik.h> #include <linux/leds.h> + #include <asm/mach-types.h> #include <asm/mach/arch.h> @@ -41,13 +51,45 @@ #include <mach/hardware.h> #include <mach/setup.h> #include <mach/devices.h> +#include <mach/sensors1p.h> +#ifdef CONFIG_INPUT_AB8500_ACCDET +#include <mach/abx500-accdet.h> +#endif #include <mach/irqs.h> +#include <mach/ste-dma40-db8500.h> +#ifdef CONFIG_U8500_SIM_DETECT +#include <mach/sim_detect.h> +#endif +#ifdef CONFIG_CRYPTO_DEV_UX500 +#include <mach/crypto-ux500.h> +#endif +#ifdef CONFIG_AV8100 +#include <video/av8100.h> +#endif + +#ifdef CONFIG_KEYBOARD_NOMADIK_SKE +#include <plat/ske.h> +#include "pins.h" +#endif #include "pins-db8500.h" -#include "ste-dma40-db8500.h" #include "devices-db8500.h" #include "board-mop500.h" #include "board-mop500-regulators.h" +#include "board-mop500-bm.h" +#if defined(CONFIG_CW1200) || defined(CONFIG_CW1200_MODULE) +#include "board-mop500-wlan.h" +#endif +#if defined(CONFIG_USB_MUSB_UX500) || defined(CONFIG_USB_MUSB_UX500_MODULE) +#include "board-ux500-usb.h" +#endif + +#ifdef CONFIG_AB8500_DENC +static struct ab8500_denc_platform_data ab8500_denc_pdata = { + .ddr_enable = true, + .ddr_little_endian = false, +}; +#endif static struct gpio_led snowball_led_array[] = { { @@ -70,7 +112,7 @@ static struct platform_device snowball_led_dev = { }; static struct ab8500_gpio_platform_data ab8500_gpio_pdata = { - .gpio_base = MOP500_AB8500_GPIO(0), + .gpio_base = AB8500_PIN_GPIO1, .irq_base = MOP500_AB8500_VIR_GPIO_IRQ_BASE, /* config_reg is the initial configuration of ab8500 pins. * The pins can be configured as GPIO or alt functions based @@ -78,19 +120,44 @@ static struct ab8500_gpio_platform_data ab8500_gpio_pdata = { * register. This is the array of 7 configuration settings. * One has to compile time decide these settings. Below is the * explanation of these setting - * GpioSel1 = 0x00 => Pins GPIO1 to GPIO8 are not used as GPIO - * GpioSel2 = 0x1E => Pins GPIO10 to GPIO13 are configured as GPIO - * GpioSel3 = 0x80 => Pin GPIO24 is configured as GPIO - * GpioSel4 = 0x01 => Pin GPIo25 is configured as GPIO - * GpioSel5 = 0x7A => Pins GPIO34, GPIO36 to GPIO39 are conf as GPIO - * GpioSel6 = 0x00 => Pins GPIO41 & GPIo42 are not configured as GPIO + * GpioSel1 = 0x0F => Pin GPIO1 (SysClkReq2) + * Pin GPIO2 (SysClkReq3) + * Pin GPIO3 (SysClkReq4) + * Pin GPIO4 (SysClkReq6) are configured as GPIO + * GpioSel2 = 0x9E => Pins GPIO10 to GPIO13 are configured as GPIO + * GpioSel3 = 0x80 => Pin GPIO24 (SysClkReq7) is configured as GPIO + * GpioSel4 = 0x01 => Pin GPIO25 (SysClkReq8) is configured as GPIO + * GpioSel5 = 0x78 => Pin GPIO36 (ApeSpiClk) + Pin GPIO37 (ApeSpiCSn) + Pin GPIO38 (ApeSpiDout) + Pin GPIO39 (ApeSpiDin) are configured as GPIO + * GpioSel6 = 0x02 => Pin GPIO42 (SysClkReq5) is configured as GPIO * AlternaFunction = 0x00 => If Pins GPIO10 to 13 are not configured - * as GPIO then this register selectes the alternate fucntions + * as GPIO then this register selectes the alternate functions */ - .config_reg = {0x00, 0x1E, 0x80, 0x01, - 0x7A, 0x00, 0x00}, + .config_reg = {0x0F, 0x9E, 0x80, 0x01, 0x78, 0x02, 0x00}, + + /* config_direction allows for the initial GPIO direction to + * be set. For Snowball we set GPIO26 to output. + */ + .config_direction = {0x00, 0x00, 0x00, 0x02, 0x00, 0x00}, + + /* + * config_pullups allows for the intial configuration of the + * GPIO pullup/pulldown configuration. + */ + .config_pullups = {0xE0, 0x01, 0x00, 0x00, 0x00, 0x00}, }; +#ifdef CONFIG_INPUT_AB8500_ACCDET +static struct abx500_accdet_platform_data ab8500_accdet_pdata = { + .btn_keycode = KEY_MEDIA, + .accdet1_dbth = ACCDET1_TH_1200mV | ACCDET1_DB_70ms, + .accdet2122_th = ACCDET21_TH_1000mV | ACCDET22_TH_1000mV, + .video_ctrl_gpio = AB8500_PIN_GPIO35, +}; +#endif + static struct gpio_keys_button snowball_key_array[] = { { .gpio = 32, @@ -182,13 +249,38 @@ static struct platform_device snowball_sbnet_dev = { }, }; +#ifdef CONFIG_MODEM_U8500 +static struct platform_device u8500_modem_dev = { + .name = "u8500-modem", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; +#endif + static struct ab8500_platform_data ab8500_platdata = { .irq_base = MOP500_AB8500_IRQ_BASE, - .regulator_reg_init = ab8500_regulator_reg_init, - .num_regulator_reg_init = ARRAY_SIZE(ab8500_regulator_reg_init), - .regulator = ab8500_regulators, - .num_regulator = ARRAY_SIZE(ab8500_regulators), + .regulator = &ab8500_regulator_plat_data, +#ifdef CONFIG_AB8500_DENC + .denc = &ab8500_denc_pdata, +#endif + .battery = &ab8500_bm_data, + .charger = &ab8500_charger_plat_data, + .btemp = &ab8500_btemp_plat_data, + .fg = &ab8500_fg_plat_data, + .chargalg = &ab8500_chargalg_plat_data, .gpio = &ab8500_gpio_pdata, +#if defined(CONFIG_USB_MUSB_UX500) || defined(CONFIG_USB_MUSB_UX500_MODULE) + .usb = &abx500_usbgpio_plat_data, +#endif +#ifdef CONFIG_INPUT_AB8500_ACCDET + .accdet = &ab8500_accdet_pdata, +#endif +#ifdef CONFIG_PM + .pm_power_off = true, +#endif + .thermal_time_out = 20, /* seconds */ }; static struct resource ab8500_resources[] = { @@ -209,14 +301,198 @@ struct platform_device ab8500_device = { .resource = ab8500_resources, }; + + +#ifdef CONFIG_KEYBOARD_NOMADIK_SKE + +/* + * Nomadik SKE keypad + */ +#define ROW_PIN_I0 164 +#define ROW_PIN_I1 163 +#define ROW_PIN_I2 162 +#define ROW_PIN_I3 161 +#define ROW_PIN_I4 156 +#define ROW_PIN_I5 155 +#define ROW_PIN_I6 154 +#define ROW_PIN_I7 153 +#define COL_PIN_O0 168 +#define COL_PIN_O1 167 +#define COL_PIN_O2 166 +#define COL_PIN_O3 165 +#define COL_PIN_O4 160 +#define COL_PIN_O5 159 +#define COL_PIN_O6 158 +#define COL_PIN_O7 157 + +static int ske_kp_rows[] = { + ROW_PIN_I0, ROW_PIN_I1, ROW_PIN_I2, ROW_PIN_I3, + ROW_PIN_I4, ROW_PIN_I5, ROW_PIN_I6, ROW_PIN_I7, +}; +static int ske_kp_cols[] = { + COL_PIN_O0, COL_PIN_O1, COL_PIN_O2, COL_PIN_O3, + COL_PIN_O4, COL_PIN_O5, COL_PIN_O6, COL_PIN_O7, +}; + +static bool ske_config; +/* + * ske_set_gpio_row: request and set gpio rows + */ +static int ske_set_gpio_row(int gpio) +{ + int ret; + + if (!ske_config) { + ret = gpio_request(gpio, "ske-kp"); + if (ret < 0) { + pr_err("ske_set_gpio_row: gpio request failed\n"); + return ret; + } + } + + ret = gpio_direction_output(gpio, 1); + if (ret < 0) { + pr_err("ske_set_gpio_row: gpio direction failed\n"); + gpio_free(gpio); + } + + return ret; +} + /* - * TPS61052 + * ske_kp_init - enable the gpio configuration */ +static int ske_kp_init(void) +{ + struct ux500_pins *pins; + int ret, i; + + pins = ux500_pins_get("ske"); + if (pins) + ux500_pins_enable(pins); + + for (i = 0; i < SKE_KPD_MAX_ROWS; i++) { + ret = ske_set_gpio_row(ske_kp_rows[i]); + if (ret < 0) { + pr_err("ske_kp_init: failed init\n"); + return ret; + } + } + if (!ske_config) + ske_config = true; + + return 0; +} + +static int ske_kp_exit(void) +{ + struct ux500_pins *pins; + + pins = ux500_pins_get("ske"); + if (pins) + ux500_pins_disable(pins); + + return 0; +} -static struct tps6105x_platform_data mop500_tps61052_data = { - .mode = TPS6105X_MODE_VOLTAGE, - .regulator_data = &tps61052_regulator, + +static const unsigned int mop500_ske_keymap[] = { +#if defined(CONFIG_KEYLAYOUT_LAYOUT1) + KEY(2, 5, KEY_END), + KEY(4, 1, KEY_HOME), + KEY(3, 5, KEY_VOLUMEDOWN), + KEY(1, 3, KEY_EMAIL), + KEY(5, 2, KEY_RIGHT), + KEY(5, 0, KEY_BACKSPACE), + + KEY(0, 5, KEY_MENU), + KEY(7, 6, KEY_ENTER), + KEY(4, 5, KEY_0), + KEY(6, 7, KEY_DOT), + KEY(3, 4, KEY_UP), + KEY(3, 3, KEY_DOWN), + + KEY(6, 4, KEY_SEND), + KEY(6, 2, KEY_BACK), + KEY(4, 2, KEY_VOLUMEUP), + KEY(5, 5, KEY_SPACE), + KEY(4, 3, KEY_LEFT), + KEY(3, 2, KEY_SEARCH), +#elif defined(CONFIG_KEYLAYOUT_LAYOUT2) + KEY(2, 5, KEY_RIGHT), + KEY(4, 1, KEY_ENTER), + KEY(3, 5, KEY_MENU), + KEY(1, 3, KEY_3), + KEY(5, 2, KEY_6), + KEY(5, 0, KEY_9), + + KEY(0, 5, KEY_UP), + KEY(7, 6, KEY_DOWN), + KEY(4, 5, KEY_0), + KEY(6, 7, KEY_2), + KEY(3, 4, KEY_5), + KEY(3, 3, KEY_8), + + KEY(6, 4, KEY_LEFT), + KEY(6, 2, KEY_BACK), + KEY(4, 2, KEY_KPDOT), + KEY(5, 5, KEY_1), + KEY(4, 3, KEY_4), + KEY(3, 2, KEY_7), +#else +#warning "No keypad layout defined." +#endif +}; + +static struct matrix_keymap_data mop500_ske_keymap_data = { + .keymap = mop500_ske_keymap, + .keymap_size = ARRAY_SIZE(mop500_ske_keymap), +}; + + + +static struct ske_keypad_platform_data mop500_ske_keypad_data = { + .init = ske_kp_init, + .exit = ske_kp_exit, + .gpio_input_pins = ske_kp_rows, + .gpio_output_pins = ske_kp_cols, + .keymap_data = &mop500_ske_keymap_data, + .no_autorepeat = true, + .krow = SKE_KPD_MAX_ROWS, /* 8x8 matrix */ + .kcol = SKE_KPD_MAX_COLS, + .debounce_ms = 20, /* in timeout period */ + .switch_delay = 200, /* in jiffies */ +}; + +#endif + + +#ifdef CONFIG_REGULATOR_FIXED_VOLTAGE +/* + * GPIO-regulator wlan vbat data + */ +static struct fixed_voltage_config snowball_gpio_wlan_vbat_data = { + .supply_name = "WLAN-VBAT", + .gpio = SNOWBALL_EN_3V6_GPIO, + .microvolts = 3600000, + .enable_high = 1, + .init_data = &gpio_wlan_vbat_regulator, + .startup_delay = 3500, /* Startup time */ +}; + +/* + * GPIO-regulator en 3v3 vbat data + */ + +static struct fixed_voltage_config snowball_gpio_en_3v3_data = { + .supply_name = "EN-3V3", + .gpio = SNOWBALL_EN_3V3_ETH_GPIO, + .microvolts = 3300000, + .enable_high = 1, + .init_data = &gpio_en_3v3_regulator, + .startup_delay = 5000, /* 1200us according to data sheet */ }; +#endif /* * TC35892 @@ -239,53 +515,64 @@ static struct tc3589x_platform_data mop500_tc35892_data = { }; static struct lp5521_led_config lp5521_pri_led[] = { - [0] = { - .chan_nr = 0, - .led_current = 0x2f, - .max_current = 0x5f, - }, - [1] = { - .chan_nr = 1, - .led_current = 0x2f, - .max_current = 0x5f, - }, - [2] = { - .chan_nr = 2, - .led_current = 0x2f, - .max_current = 0x5f, - }, + [0] = { + .chan_nr = 0, + .led_current = 0x2f, + .max_current = 0x5f, + }, + [1] = { + .chan_nr = 1, + .led_current = 0x2f, + .max_current = 0x5f, + }, + [2] = { + .chan_nr = 2, + .led_current = 0x2f, + .max_current = 0x5f, + }, +}; + +#ifdef CONFIG_AV8100 +static struct av8100_platform_data av8100_plat_data = { + .irq = NOMADIK_GPIO_TO_IRQ(192), + .reset = MOP500_HDMI_RST_GPIO, + .inputclk_id = "sysclk2", + .regulator_pwr_id = "hdmi_1v8", + .alt_powerupseq = true, + .mclk_freq = 3, /* MCLK_RNG_31_38 */ }; +#endif static struct lp5521_platform_data __initdata lp5521_pri_data = { - .label = "lp5521_pri", - .led_config = &lp5521_pri_led[0], - .num_channels = 3, - .clock_mode = LP5521_CLOCK_EXT, + .label = "lp5521_pri", + .led_config = &lp5521_pri_led[0], + .num_channels = 3, + .clock_mode = LP5521_CLOCK_EXT, }; static struct lp5521_led_config lp5521_sec_led[] = { - [0] = { - .chan_nr = 0, - .led_current = 0x2f, - .max_current = 0x5f, - }, - [1] = { - .chan_nr = 1, - .led_current = 0x2f, - .max_current = 0x5f, - }, - [2] = { - .chan_nr = 2, - .led_current = 0x2f, - .max_current = 0x5f, - }, + [0] = { + .chan_nr = 0, + .led_current = 0x2f, + .max_current = 0x5f, + }, + [1] = { + .chan_nr = 1, + .led_current = 0x2f, + .max_current = 0x5f, + }, + [2] = { + .chan_nr = 2, + .led_current = 0x2f, + .max_current = 0x5f, + }, }; static struct lp5521_platform_data __initdata lp5521_sec_data = { - .label = "lp5521_sec", - .led_config = &lp5521_sec_led[0], - .num_channels = 3, - .clock_mode = LP5521_CLOCK_EXT, + .label = "lp5521_sec", + .led_config = &lp5521_sec_led[0], + .num_channels = 3, + .clock_mode = LP5521_CLOCK_EXT, }; static struct i2c_board_info __initdata mop500_i2c0_devices[] = { @@ -294,15 +581,26 @@ static struct i2c_board_info __initdata mop500_i2c0_devices[] = { .irq = NOMADIK_GPIO_TO_IRQ(217), .platform_data = &mop500_tc35892_data, }, - /* I2C0 devices only available prior to HREFv60 */ +#ifdef CONFIG_AV8100 { - I2C_BOARD_INFO("tps61052", 0x33), - .platform_data = &mop500_tps61052_data, + I2C_BOARD_INFO("av8100", 0x70), + .platform_data = &av8100_plat_data, }, +#endif + /* I2C0 devices only available prior to HREFv60 */ }; #define NUM_PRE_V60_I2C0_DEVICES 1 +static struct i2c_board_info __initdata snowball_i2c0_devices[] = { +#ifdef CONFIG_AV8100 + { + I2C_BOARD_INFO("av8100", 0x70), + .platform_data = &av8100_plat_data, + }, +#endif +}; + static struct i2c_board_info __initdata mop500_i2c2_devices[] = { { /* lp5521 LED driver, 1st device */ @@ -343,13 +641,13 @@ static struct nmk_i2c_controller u8500_i2c##id##_data = { \ /* * The board uses 4 i2c controllers, initialize all of * them with slave data setup time of 250 ns, - * Tx & Rx FIFO threshold values as 8 and standard + * Tx & Rx FIFO threshold values as 1 and standard * mode of operation */ -U8500_I2C_CONTROLLER(0, 0xe, 1, 8, 100000, 200, I2C_FREQ_MODE_FAST); -U8500_I2C_CONTROLLER(1, 0xe, 1, 8, 100000, 200, I2C_FREQ_MODE_FAST); -U8500_I2C_CONTROLLER(2, 0xe, 1, 8, 100000, 200, I2C_FREQ_MODE_FAST); -U8500_I2C_CONTROLLER(3, 0xe, 1, 8, 100000, 200, I2C_FREQ_MODE_FAST); +U8500_I2C_CONTROLLER(0, 0xe, 1, 8, 400000, 200, I2C_FREQ_MODE_FAST); +U8500_I2C_CONTROLLER(1, 0xe, 1, 8, 400000, 200, I2C_FREQ_MODE_FAST); +U8500_I2C_CONTROLLER(2, 0xe, 1, 8, 400000, 200, I2C_FREQ_MODE_FAST); +U8500_I2C_CONTROLLER(3, 0xe, 1, 8, 400000, 200, I2C_FREQ_MODE_FAST); static void __init mop500_i2c_init(void) { @@ -366,18 +664,25 @@ static struct gpio_keys_button mop500_gpio_keys[] = { .code = SW_FRONT_PROXIMITY, .active_low = 0, .can_disable = 1, + }, + { + .desc = "HED54XXU11 Hall Effect Sensor", + .type = EV_SW, + .code = SW_LID, /* FIXME arbitrary usage */ + .active_low = 0, + .can_disable = 1, } }; -static struct regulator *prox_regulator; -static int mop500_prox_activate(struct device *dev); -static void mop500_prox_deactivate(struct device *dev); +static struct regulator *sensors1p_regulator; +static int mop500_sensors1p_activate(struct device *dev); +static void mop500_sensors1p_deactivate(struct device *dev); static struct gpio_keys_platform_data mop500_gpio_keys_data = { .buttons = mop500_gpio_keys, .nbuttons = ARRAY_SIZE(mop500_gpio_keys), - .enable = mop500_prox_activate, - .disable = mop500_prox_deactivate, + .enable = mop500_sensors1p_activate, + .disable = mop500_sensors1p_deactivate, }; static struct platform_device mop500_gpio_keys_device = { @@ -388,31 +693,312 @@ static struct platform_device mop500_gpio_keys_device = { }, }; -static int mop500_prox_activate(struct device *dev) +#ifdef CONFIG_REGULATOR_FIXED_VOLTAGE +static struct platform_device snowball_gpio_wlan_vbat_regulator_device = { + .name = "reg-fixed-voltage", + .id = 0, + .dev = { + .platform_data = &snowball_gpio_wlan_vbat_data, + }, +}; + +static struct platform_device snowball_gpio_en_3v3_regulator_device = { + .name = "reg-fixed-voltage", + .id = 1, + .dev = { + .platform_data = &snowball_gpio_en_3v3_data, + }, +}; +#endif + +static int mop500_sensors1p_activate(struct device *dev) { - prox_regulator = regulator_get(&mop500_gpio_keys_device.dev, + sensors1p_regulator = regulator_get(&mop500_gpio_keys_device.dev, "vcc"); - if (IS_ERR(prox_regulator)) { - dev_err(&mop500_gpio_keys_device.dev, - "no regulator\n"); - return PTR_ERR(prox_regulator); + if (IS_ERR(sensors1p_regulator)) { + dev_err(&mop500_gpio_keys_device.dev, "no regulator\n"); + return PTR_ERR(sensors1p_regulator); } - regulator_enable(prox_regulator); + regulator_enable(sensors1p_regulator); return 0; } -static void mop500_prox_deactivate(struct device *dev) +static void mop500_sensors1p_deactivate(struct device *dev) { - regulator_disable(prox_regulator); - regulator_put(prox_regulator); + regulator_disable(sensors1p_regulator); + regulator_put(sensors1p_regulator); } +#ifdef CONFIG_LEDS_PWM +static struct led_pwm pwm_leds_data[] = { + [0] = { + .name = "lcd-backlight", + .pwm_id = 1, + .max_brightness = 255, + .lth_brightness = 90, + .pwm_period_ns = 1023, + }, +#ifdef CONFIG_DISPLAY_GENERIC_DSI_SECONDARY + [1] = { + .name = "sec-lcd-backlight", + .pwm_id = 2, + .max_brightness = 255, + .lth_brightness = 90, + .pwm_period_ns = 1023, + }, +#endif +}; + +static struct led_pwm_platform_data u8500_leds_data = { +#ifdef CONFIG_DISPLAY_GENERIC_DSI_SECONDARY + .num_leds = 2, +#else + .num_leds = 1, +#endif + .leds = pwm_leds_data, +}; + +static struct platform_device ux500_leds_device = { + .name = "leds_pwm", + .dev = { + .platform_data = &u8500_leds_data, + }, +}; +#endif + +#ifdef CONFIG_BACKLIGHT_PWM +static struct platform_pwm_backlight_data u8500_backlight_data[] = { + [0] = { + .pwm_id = 1, + .max_brightness = 255, + .dft_brightness = 200, + .lth_brightness = 90, + .pwm_period_ns = 1023, + }, + [1] = { + .pwm_id = 2, + .max_brightness = 255, + .dft_brightness = 200, + .lth_brightness = 90, + .pwm_period_ns = 1023, + }, +}; + +static struct platform_device ux500_backlight_device[] = { + [0] = { + .name = "pwm-backlight", + .id = 0, + .dev = { + .platform_data = &u8500_backlight_data[0], + }, + }, + [1] = { + .name = "pwm-backlight", + .id = 1, + .dev = { + .platform_data = &u8500_backlight_data[1], + }, + }, +}; +#endif + +/* Force feedback vibrator device */ +static struct platform_device ste_ff_vibra_device = { + .name = "ste_ff_vibra" +}; + +#ifdef CONFIG_HSI +static struct hsi_board_info __initdata u8500_hsi_devices[] = { + { + .name = "hsi_char", + .hsi_id = 0, + .port = 0, + .tx_cfg = { + .mode = HSI_MODE_FRAME, + .channels = 1, + .speed = 200000, + {.arb_mode = HSI_ARB_RR}, + }, + .rx_cfg = { + .mode = HSI_MODE_FRAME, + .channels = 1, + .speed = 200000, + {.flow = HSI_FLOW_SYNC}, + }, + }, + { + .name = "hsi_test", + .hsi_id = 0, + .port = 0, + .tx_cfg = { + .mode = HSI_MODE_FRAME, + .channels = 2, + .speed = 100000, + {.arb_mode = HSI_ARB_RR}, + }, + .rx_cfg = { + .mode = HSI_MODE_FRAME, + .channels = 2, + .speed = 200000, + {.flow = HSI_FLOW_SYNC}, + }, + }, + { + .name = "cfhsi_v3_driver", + .hsi_id = 0, + .port = 0, + .tx_cfg = { + .mode = HSI_MODE_STREAM, + .channels = 2, + .speed = 20000, + {.arb_mode = HSI_ARB_RR}, + }, + .rx_cfg = { + .mode = HSI_MODE_STREAM, + .channels = 2, + .speed = 200000, + {.flow = HSI_FLOW_SYNC}, + }, + }, +}; +#endif + +#ifdef CONFIG_U8500_SIM_DETECT +static struct sim_detect_platform_data sim_detect_pdata = { + .irq_num = MOP500_AB8500_VIR_GPIO_IRQ(6), +}; +struct platform_device u8500_sim_detect_device = { + .name = "sim-detect", + .id = 0, + .dev = { + .platform_data = &sim_detect_pdata, + }, +}; +#endif + +#ifdef CONFIG_SENSORS1P_MOP +static struct sensors1p_config sensors1p_config = { + /* SFH7741 */ + .proximity = { + .pin = EGPIO_PIN_7, + .startup_time = 120, /* ms */ + .regulator = "v-proximity", + }, + /* HED54XXU11 */ + .hal = { + .pin = EGPIO_PIN_8, + .startup_time = 100, /* Actually, I have no clue. */ + .regulator = "v-hal", + }, +}; + +struct platform_device sensors1p_device = { + .name = "sensors1p", + .dev = { + .platform_data = (void *)&sensors1p_config, + }, +}; +#endif + +#ifdef CONFIG_CRYPTO_DEV_UX500 +static struct cryp_platform_data u8500_cryp1_platform_data = { + .mem_to_engine = { + .dir = STEDMA40_MEM_TO_PERIPH, + .src_dev_type = STEDMA40_DEV_SRC_MEMORY, + .dst_dev_type = DB8500_DMA_DEV48_CAC1_TX, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, + .mode = STEDMA40_MODE_LOGICAL, + .src_info.psize = STEDMA40_PSIZE_LOG_4, + .dst_info.psize = STEDMA40_PSIZE_LOG_4, + }, + .engine_to_mem = { + .dir = STEDMA40_PERIPH_TO_MEM, + .src_dev_type = DB8500_DMA_DEV48_CAC1_RX, + .dst_dev_type = STEDMA40_DEV_DST_MEMORY, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, + .mode = STEDMA40_MODE_LOGICAL, + .src_info.psize = STEDMA40_PSIZE_LOG_4, + .dst_info.psize = STEDMA40_PSIZE_LOG_4, + } +}; + +static struct hash_platform_data u8500_hash1_platform_data = { + .mem_to_engine = { + .dir = STEDMA40_MEM_TO_PERIPH, + .src_dev_type = STEDMA40_DEV_SRC_MEMORY, + .dst_dev_type = DB8500_DMA_DEV50_HAC1_TX, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, + .mode = STEDMA40_MODE_LOGICAL, + .src_info.psize = STEDMA40_PSIZE_LOG_16, + .dst_info.psize = STEDMA40_PSIZE_LOG_16, + }, +}; +#endif + /* add any platform devices here - TODO */ static struct platform_device *mop500_platform_devs[] __initdata = { +#ifdef CONFIG_SENSORS1P_MOP + &sensors1p_device, +#endif +#ifdef CONFIG_U8500_SIM_DETECT + &u8500_sim_detect_device, +#endif + &u8500_shrm_device, + &ste_ff_vibra_device, +#ifdef CONFIG_U8500_MMIO + &ux500_mmio_device, +#endif + &ux500_hwmem_device, +#ifdef CONFIG_FB_MCDE + &u8500_mcde_device, +#endif + &u8500_b2r2_device, + &u8500_thsens_device, +#ifdef CONFIG_STE_TRACE_MODEM + &u8500_trace_modem, +#endif &mop500_gpio_keys_device, - &ab8500_device, +#ifdef CONFIG_LEDS_PWM + &ux500_leds_device, +#endif +#ifdef CONFIG_BACKLIGHT_PWM + &ux500_backlight_device[0], + &ux500_backlight_device[1], +#endif +#ifdef CONFIG_DB8500_MLOADER + &mloader_fw_device, +#endif +#ifdef CONFIG_HSI + &u8500_hsi_device, +#endif +#ifdef CONFIG_MODEM_U8500 + &u8500_modem_dev, +#endif +}; + +#ifdef CONFIG_STM_MSP_I2S +/* + * MSP-SPI + */ + +#define NUM_MSP_CLIENTS 10 + +static struct stm_msp_controller mop500_msp2_spi_data = { + .id = 2, + .num_chipselect = NUM_MSP_CLIENTS, + .base_addr = U8500_MSP2_BASE, + .device_name = "msp2", }; +/* + * SSP + */ + +#define NUM_SSP_CLIENTS 10 + #ifdef CONFIG_STE_DMA40 static struct stedma40_chan_cfg ssp0_dma_cfg_rx = { .mode = STEDMA40_MODE_LOGICAL, @@ -434,27 +1020,33 @@ static struct stedma40_chan_cfg ssp0_dma_cfg_tx = { #endif static struct pl022_ssp_controller ssp0_platform_data = { - .bus_id = 0, + .bus_id = 4, #ifdef CONFIG_STE_DMA40 .enable_dma = 1, .dma_filter = stedma40_filter, .dma_rx_param = &ssp0_dma_cfg_rx, .dma_tx_param = &ssp0_dma_cfg_tx, -#else - .enable_dma = 0, #endif /* on this platform, gpio 31,142,144,214 & * 224 are connected as chip selects */ - .num_chipselect = 5, + .num_chipselect = NUM_SSP_CLIENTS, }; + static void __init mop500_spi_init(void) { db8500_add_ssp0(&ssp0_platform_data); + if (!machine_is_snowball()) + db8500_add_msp2_spi(&mop500_msp2_spi_data); } +#else +static void __init mop500_spi_init(void) +{ +} +#endif /* CONFIG_STM_MSP_I2S */ -#ifdef CONFIG_STE_DMA40 +#ifdef CONFIG_STE_DMA40_REMOVE static struct stedma40_chan_cfg uart0_dma_cfg_rx = { .mode = STEDMA40_MODE_LOGICAL, .dir = STEDMA40_PERIPH_TO_MEM, @@ -520,6 +1112,7 @@ static pin_cfg_t mop500_pins_uart0[] = { #define PRCC_K_SOFTRST_SET 0x18 #define PRCC_K_SOFTRST_CLEAR 0x1C +/* pl011 reset */ static void ux500_uart0_reset(void) { void __iomem *prcc_rst_set, *prcc_rst_clr; @@ -558,8 +1151,10 @@ static void ux500_uart0_exit(void) pr_err("pl011: uart pins_disable failed\n"); } + + static struct amba_pl011_data uart0_plat = { -#ifdef CONFIG_STE_DMA40 +#ifdef CONFIG_STE_DMA40_REMOVE .dma_filter = stedma40_filter, .dma_rx_param = &uart0_dma_cfg_rx, .dma_tx_param = &uart0_dma_cfg_tx, @@ -570,7 +1165,7 @@ static struct amba_pl011_data uart0_plat = { }; static struct amba_pl011_data uart1_plat = { -#ifdef CONFIG_STE_DMA40 +#ifdef CONFIG_STE_DMA40_REMOVE .dma_filter = stedma40_filter, .dma_rx_param = &uart1_dma_cfg_rx, .dma_tx_param = &uart1_dma_cfg_tx, @@ -578,7 +1173,7 @@ static struct amba_pl011_data uart1_plat = { }; static struct amba_pl011_data uart2_plat = { -#ifdef CONFIG_STE_DMA40 +#ifdef CONFIG_STE_DMA40_REMOVE .dma_filter = stedma40_filter, .dma_rx_param = &uart2_dma_cfg_rx, .dma_tx_param = &uart2_dma_cfg_tx, @@ -592,33 +1187,77 @@ static void __init mop500_uart_init(void) db8500_add_uart2(&uart2_plat); } +#ifdef CONFIG_CRYPTO_DEV_UX500 +static void __init u8500_cryp1_hash1_init(void) +{ + db8500_add_cryp1(&u8500_cryp1_platform_data); + db8500_add_hash1(&u8500_hash1_platform_data); +} +#endif + static struct platform_device *snowball_platform_devs[] __initdata = { + &ux500_hwmem_device, &snowball_led_dev, &snowball_key_dev, +#ifdef CONFIG_REGULATOR_FIXED_VOLTAGE + &snowball_gpio_en_3v3_regulator_device, +#endif +#ifdef CONFIG_REGULATOR_FIXED_VOLTAGE + &snowball_gpio_wlan_vbat_regulator_device, +#endif &snowball_sbnet_dev, - &ab8500_device, +#ifdef CONFIG_FB_MCDE + &u8500_mcde_device, +#endif + &u8500_b2r2_device, }; -static void __init mop500_init_machine(void) +/* + * On boards hrefpv60 and later, the accessory insertion/removal, + * button press/release are inverted. +*/ +static void accessory_detect_config(void) { - int i2c0_devs; +#ifdef CONFIG_INPUT_AB8500_ACCDET + if (machine_is_hrefv60()) + ab8500_accdet_pdata.is_detection_inverted = true; + else + ab8500_accdet_pdata.is_detection_inverted = false; +#endif +} +static void __init mop500_init_machine(void) +{ /* * The HREFv60 board removed a GPIO expander and routed * all these GPIO pins to the internal GPIO controller * instead. */ if (!machine_is_snowball()) { - if (machine_is_hrefv60()) + if (machine_is_hrefv60()) { mop500_gpio_keys[0].gpio = HREFV60_PROX_SENSE_GPIO; - else + mop500_gpio_keys[1].gpio = HREFV60_HAL_SW_GPIO; + } else { mop500_gpio_keys[0].gpio = GPIO_PROX_SENSOR; + mop500_gpio_keys[1].gpio = GPIO_HAL_SENSOR; + } } + accessory_detect_config(); + u8500_init_devices(); mop500_pins_init(); +#ifdef CONFIG_CRYPTO_DEV_UX500 + u8500_cryp1_hash1_init(); +#endif + +#ifdef CONFIG_HSI + hsi_register_board_info(u8500_hsi_devices, + ARRAY_SIZE(u8500_hsi_devices)); +#endif + if (machine_is_snowball()) platform_add_devices(snowball_platform_devs, ARRAY_SIZE(snowball_platform_devs)); @@ -630,13 +1269,30 @@ static void __init mop500_init_machine(void) mop500_sdi_init(); mop500_spi_init(); mop500_uart_init(); +#ifdef CONFIG_STM_MSP_I2S + mop500_msp_init(); +#endif +#if defined(CONFIG_CW1200) || defined(CONFIG_CW1200_MODULE) + mop500_wlan_init(); +#endif - i2c0_devs = ARRAY_SIZE(mop500_i2c0_devices); - if (machine_is_hrefv60()) - i2c0_devs -= NUM_PRE_V60_I2C0_DEVICES; +#ifdef CONFIG_KEYBOARD_NOMADIK_SKE + db8500_add_ske_keypad(&mop500_ske_keypad_data); +#endif + + platform_device_register(&ab8500_device); + + /* Snowball jus have the av8100 on i2c0 */ + if (!machine_is_snowball()) + i2c_register_board_info(0, mop500_i2c0_devices, + ARRAY_SIZE(mop500_i2c0_devices)); + else + i2c_register_board_info(0, snowball_i2c0_devices, + ARRAY_SIZE(snowball_i2c0_devices)); - i2c_register_board_info(0, mop500_i2c0_devices, i2c0_devs); - i2c_register_board_info(2, mop500_i2c2_devices, + /* Snowball dont have any of these */ + if (!machine_is_snowball()) + i2c_register_board_info(2, mop500_i2c2_devices, ARRAY_SIZE(mop500_i2c2_devices)); /* This board has full regulator constraints */ @@ -661,7 +1317,7 @@ MACHINE_START(HREFV60, "ST-Ericsson U8500 Platform HREFv60+") .init_machine = mop500_init_machine, MACHINE_END -MACHINE_START(SNOWBALL, "Calao Systems Snowball platform") +MACHINE_START(SNOWBALL, "ST-Ericsson Snowball platform") .boot_params = 0x100, .map_io = u8500_map_io, .init_irq = ux500_init_irq, diff --git a/arch/arm/mach-ux500/board-mop500.h b/arch/arm/mach-ux500/board-mop500.h index ee77a8970c3..829a7bece6e 100644 --- a/arch/arm/mach-ux500/board-mop500.h +++ b/arch/arm/mach-ux500/board-mop500.h @@ -7,10 +7,19 @@ #ifndef __BOARD_MOP500_H #define __BOARD_MOP500_H -/* snowball GPIO for MMC card */ -#define SNOWBALL_SDMMC_EN_GPIO 217 -#define SNOWBALL_SDMMC_1V8_3V_GPIO 228 -#define SNOWBALL_SDMMC_CD_GPIO 218 +/* This defines the NOMADIK_NR_GPIO */ +#include <linux/mfd/ab8500/gpio.h> +#include <mach/gpio.h> + +/* Snowball GPIO for MMC card */ +#define SNOWBALL_SDMMC_EN_GPIO 217 +#define SNOWBALL_SDMMC_1V8_3V_GPIO 228 +#define SNOWBALL_SDMMC_CD_GPIO 218 + +/* Snowball specific GPIO assignments, this board has no GPIO expander */ +#define SNOWBALL_ACCEL_INT1_GPIO 163 +#define SNOWBALL_ACCEL_INT2_GPIO 164 +#define SNOWBALL_MAGNET_DRDY_GPIO 165 /* HREFv60-specific GPIO assignments, this board has no GPIO expander */ #define HREFV60_TOUCH_RST_GPIO 143 @@ -24,18 +33,43 @@ #define HREFV60_MAGNET_DRDY_GPIO 32 #define HREFV60_DISP1_RST_GPIO 65 #define HREFV60_DISP2_RST_GPIO 66 +#define HREFV60_MMIO_XENON_CHARGE 170 +#define HREFV60_XSHUTDOWN_SECONDARY_SENSOR 140 +#define HREFV60_CAMERA_FLASH_ENABLE 22 +#define XSHUTDOWN_PRIMARY_SENSOR 141 +#define XSHUTDOWN_SECONDARY_SENSOR 142 +#define CAMERA_FLASH_INT_PIN 7 +#define CYPRESS_TOUCH_INT_PIN 84 +#define CYPRESS_TOUCH_RST_GPIO 143 +#define CYPRESS_SLAVE_SELECT_GPIO 216 + +/* MOP500 generic GPIOs */ +#define MOP500_HDMI_RST_GPIO 196 /* GPIOs on the TC35892 expander */ -#define MOP500_EGPIO(x) (NOMADIK_NR_GPIO + (x)) +#define GPIO_MAGNET_DRDY MOP500_EGPIO(1) #define GPIO_SDMMC_CD MOP500_EGPIO(3) +#define GPIO_CAMERA_FLASH_ENABLE MOP500_EGPIO(4) +#define GPIO_MMIO_XENON_CHARGE MOP500_EGPIO(5) #define GPIO_PROX_SENSOR MOP500_EGPIO(7) +#define GPIO_HAL_SENSOR MOP500_EGPIO(8) +#define GPIO_ACCEL_INT1 MOP500_EGPIO(10) +#define GPIO_ACCEL_INT2 MOP500_EGPIO(11) #define GPIO_BU21013_CS MOP500_EGPIO(13) +#define MOP500_DISP2_RST_GPIO MOP500_EGPIO(14) +#define MOP500_DISP1_RST_GPIO MOP500_EGPIO(15) #define GPIO_SDMMC_EN MOP500_EGPIO(17) #define GPIO_SDMMC_1V8_3V_SEL MOP500_EGPIO(18) -#define MOP500_EGPIO_END MOP500_EGPIO(24) -/* GPIOs on the AB8500 mixed-signals circuit */ -#define MOP500_AB8500_GPIO(x) (MOP500_EGPIO_END + (x)) +/*Snowball AB8500 GPIO */ +#define SNOWBALL_VSMPS2_1V8_GPIO AB8500_PIN_GPIO1 /* SYSCLKREQ2/GPIO1 */ +#define SNOWBALL_PM_GPIO1_GPIO AB8500_PIN_GPIO2 /* SYSCLKREQ3/GPIO2 */ +#define SNOWBALL_WLAN_CLK_REQ_GPIO AB8500_PIN_GPIO3 /* SYSCLKREQ4/GPIO3 */ +#define SNOWBALL_PM_GPIO4_GPIO AB8500_PIN_GPIO4 /* SYSCLKREQ6/GPIO4 */ +#define SNOWBALL_EN_3V6_GPIO AB8500_PIN_GPIO16 /* PWMOUT3/GPIO16 */ +#define SNOWBALL_PME_ETH_GPIO AB8500_PIN_GPIO24 /* SYSCLKREQ7/GPIO24 */ +#define SNOWBALL_EN_3V3_ETH_GPIO AB8500_PIN_GPIO26 /* GPIO26 */ + struct i2c_board_info; @@ -43,9 +77,19 @@ extern void mop500_sdi_init(void); extern void mop500_sdi_tc35892_init(void); void __init mop500_u8500uib_init(void); void __init mop500_stuib_init(void); +void __init mop500_msp_init(void); void __init mop500_pins_init(void); +void mop500_cyttsp_init(void); +void __init mop500_u8500uib_r3_init(void); -void mop500_uib_i2c_add(int busnum, struct i2c_board_info *info, +void mop500_uib_i2c_add(int busnum, struct i2c_board_info const *info, unsigned n); +int msp13_i2s_init(void); +int msp13_i2s_exit(void); + +int uib_is_stuib(void); +int uib_is_u8500uib(void); +int uib_is_u8500uibr3(void); + #endif diff --git a/arch/arm/mach-ux500/board-pins-sleep-force.c b/arch/arm/mach-ux500/board-pins-sleep-force.c new file mode 100644 index 00000000000..bef6f4e6df4 --- /dev/null +++ b/arch/arm/mach-ux500/board-pins-sleep-force.c @@ -0,0 +1,267 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/string.h> + +#include <linux/gpio/nomadik.h> +#include <mach/hardware.h> + +#include "board-pins-sleep-force.h" +#include "pins-db8500.h" +#include "pins.h" + +static u32 u8500_gpio_banks[] = {U8500_GPIOBANK0_BASE, + U8500_GPIOBANK1_BASE, + U8500_GPIOBANK2_BASE, + U8500_GPIOBANK3_BASE, + U8500_GPIOBANK4_BASE, + U8500_GPIOBANK5_BASE, + U8500_GPIOBANK6_BASE, + U8500_GPIOBANK7_BASE, + U8500_GPIOBANK8_BASE}; + +/* + * This function is called to force gpio power save + * settings during suspend. + */ +void sleep_pins_config_pm(pin_cfg_t *cfgs, int num) +{ + int i = 0; + int gpio = 0; + u32 w_imsc = 0; + u32 imsc = 0; + u32 offset; + u32 bitmask = 1; + u32 dirs_register = 0; + u32 dirc_register = 0; + u32 dats_register = 0; + u32 datc_register = 0; + u32 pdis_register_disable = 0; + u32 pdis_register_enabled = 0; + u32 slpm_register_disabled = 0; + u32 slpm_register_enabled = 0; + u32 bankaddr = 0; + + gpio = PIN_NUM(cfgs[i]); + + /* Get the bank number the pin is mapped to */ + bankaddr = IO_ADDRESS(u8500_gpio_banks[(gpio >> GPIO_BLOCK_SHIFT)]); + + w_imsc = readl(bankaddr + NMK_GPIO_RWIMSC) | + readl(bankaddr + NMK_GPIO_FWIMSC); + + imsc = readl(bankaddr + NMK_GPIO_RIMSC) | + readl(bankaddr + NMK_GPIO_FIMSC); + + for (i = 0; i < num; i++) { + /* Get the pin number */ + gpio = PIN_NUM(cfgs[i]); + + /* get the offest into the register */ + offset = gpio % NMK_GPIO_PER_CHIP; + /* Set the bit to toggle */ + bitmask = 1 << offset ; + + /* Next we check for direction (INPUT/OUTPUT) */ + switch (PIN_SLPM_DIR(cfgs[i])) { + case GPIO_IS_INPUT: + /* GPIO is set to input */ + dirc_register |= bitmask; + + /* + * Next check for pull (PULLUP/PULLDOWN) + * and configure accordingly. + */ + switch (PIN_SLPM_PULL(cfgs[i])) { + case GPIO_PULL_UPDOWN_DISABLED: + pdis_register_disable |= bitmask; + break; + + case GPIO_IS_PULLUP: + dats_register |= bitmask; + pdis_register_enabled |= bitmask; + break; + + case GPIO_IS_PULLDOWN: + datc_register |= bitmask; + pdis_register_enabled |= bitmask; + break; + + case GPIO_PULL_NO_CHANGE: + break; + + default: + BUG(); + break; + + } + break; + + case GPIO_IS_OUTPUT: + /* GPIO is set to output */ + dirs_register |= bitmask; + + /* + * Since its output there should not + * be a need to disable PULL UP/DOWN + * but better safe than sorry. + */ + pdis_register_disable |= bitmask; + /* Next we check for setting GPIO HIGH/LOW */ + switch (PIN_SLPM_VAL(cfgs[i])) { + case GPIO_IS_OUTPUT_LOW: + /* GPIO is set to LOW */ + datc_register |= bitmask; + break; + + case GPIO_IS_OUTPUT_HIGH: + /* GPIO is set to high */ + dats_register |= bitmask; + break; + + case GPIO_IS_NO_CHANGE: + break; + + default: + BUG(); + break; + } + + break; + case GPIO_IS_NOT_CHANGED: + break; + + default: + BUG(); + break; + } + + /* Next check for Sleep Power Managment (SLPM) */ + switch (PIN_SLPM(cfgs[i])) { + case GPIO_WAKEUP_IS_ENABLED: + slpm_register_enabled |= bitmask; + break; + + case GPIO_WAKEUP_IS_DISBLED: + slpm_register_disabled |= bitmask; + break; + + default: + BUG(); + break; + } + + /* Next check for Sleep Power Managment (SLPM) */ + switch (PIN_SLPM_PDIS(cfgs[i])) { + case GPIO_PDIS_NO_CHANGE: + break; + + case GPIO_PDIS_DISABLED: + pdis_register_disable |= bitmask; + break; + + case GPIO_PDIS_ENABLED: + pdis_register_enabled |= bitmask; + break; + + default: + BUG(); + break; + } + + } + + /* Write the register settings GPIO direction */ + writel(dirs_register & ~w_imsc, bankaddr + NMK_GPIO_DIRS); + writel(dirc_register, bankaddr + NMK_GPIO_DIRC); + + writel(datc_register & ~w_imsc, bankaddr + NMK_GPIO_DATC); + writel(dats_register & ~w_imsc, bankaddr + NMK_GPIO_DATS); + + /* Write the PDIS enable/disable */ + writel(readl(bankaddr + NMK_GPIO_PDIS) + | (pdis_register_disable & ~w_imsc & ~imsc), bankaddr + NMK_GPIO_PDIS); + writel(readl(bankaddr + NMK_GPIO_PDIS) + & (~pdis_register_enabled & ~w_imsc & ~imsc), bankaddr + NMK_GPIO_PDIS); + + /* Write the SLPM enable/disable */ + writel(readl(bankaddr + NMK_GPIO_SLPC) | slpm_register_disabled, + bankaddr + NMK_GPIO_SLPC); + writel(readl(bankaddr + NMK_GPIO_SLPC) & ~slpm_register_enabled, + bankaddr + NMK_GPIO_SLPC); +} + +void sleep_pins_config_pm_mux(pin_cfg_t *cfgs, int num) +{ + int i = 0; + int gpio = 0; + u32 offset; + u32 bitmask = 1; + u32 gpio_afsla_register_set = 0; + u32 gpio_afslb_register_set = 0; + u32 gpio_afsla_register_clear = 0; + u32 gpio_afslb_register_clear = 0; + u32 bankaddr = 0; + + gpio = PIN_NUM(cfgs[i]); + + /* Get the bank number the pin is mapped to */ + bankaddr = IO_ADDRESS(u8500_gpio_banks[(gpio >> GPIO_BLOCK_SHIFT)]); + + for (i = 0; i < num; i++) { + /* Get the pin number */ + gpio = PIN_NUM(cfgs[i]); + + /* get the offset into the register */ + offset = gpio % NMK_GPIO_PER_CHIP; + /* Set the bit to toggle */ + bitmask = 1 << offset ; + + /* First check for ALT pin configuration */ + switch (PIN_ALT(cfgs[i])) { + case NMK_GPIO_ALT_GPIO: + /* Set bit to configured as GPIO */ + gpio_afsla_register_clear |= bitmask; + gpio_afslb_register_clear |= bitmask; + break; + + case NMK_GPIO_ALT_A: + /* ALT A setting so set corresponding bit */ + gpio_afsla_register_set |= bitmask; + break; + + case NMK_GPIO_ALT_B: + /* ALT B setting so set corresponding bit */ + gpio_afslb_register_set |= bitmask; + break; + + case NMK_GPIO_ALT_C: + /* ALT C setting so set corresponding bits */ + gpio_afsla_register_set |= bitmask; + gpio_afslb_register_set |= bitmask; + break; + + default: + BUG(); + break; + } + } + /* Set bits that configures GPIO */ + writel(readl(bankaddr + NMK_GPIO_AFSLA) + & ~gpio_afsla_register_clear, bankaddr + NMK_GPIO_AFSLA); + writel(readl(bankaddr + NMK_GPIO_AFSLB) + & ~gpio_afslb_register_clear, bankaddr + NMK_GPIO_AFSLB); + + /* Set bits that configures ALT_X */ + writel(readl(bankaddr + NMK_GPIO_AFSLA) + | gpio_afsla_register_set, bankaddr + NMK_GPIO_AFSLA); + writel(readl(bankaddr + NMK_GPIO_AFSLB) + | gpio_afslb_register_set, bankaddr + NMK_GPIO_AFSLB); +} diff --git a/arch/arm/mach-ux500/board-pins-sleep-force.h b/arch/arm/mach-ux500/board-pins-sleep-force.h new file mode 100644 index 00000000000..0949c9bfcda --- /dev/null +++ b/arch/arm/mach-ux500/board-pins-sleep-force.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef __BOARD_PINS_SLEEP_FORCE_H +#define __BOARD_PINS_SLEEP_FORCE_H + +#include <plat/pincfg.h> + +#define NMK_GPIO_PER_CHIP 32 +#define GPIO_BLOCK_SHIFT 5 + +#define GPIO_IS_NOT_CHANGED 0 +#define GPIO_IS_INPUT 1 +#define GPIO_IS_OUTPUT 2 + +#define GPIO_WAKEUP_IS_ENABLED 0 +#define GPIO_WAKEUP_IS_DISBLED 1 + +#define GPIO_IS_NO_CHANGE 0 +#define GPIO_IS_OUTPUT_LOW 1 +#define GPIO_IS_OUTPUT_HIGH 2 + +#define GPIO_PULL_NO_CHANGE 0 +#define GPIO_PULL_UPDOWN_DISABLED 1 +#define GPIO_IS_PULLUP 2 +#define GPIO_IS_PULLDOWN 3 + +#define GPIO_PDIS_NO_CHANGE 0 +#define GPIO_PDIS_DISABLED 1 +#define GPIO_PDIS_ENABLED 2 + +void sleep_pins_config_pm_mux(pin_cfg_t *cfgs, int num); +void sleep_pins_config_pm(pin_cfg_t *cfgs, int num); + +#endif diff --git a/arch/arm/mach-ux500/board-u5500-bm.c b/arch/arm/mach-ux500/board-u5500-bm.c new file mode 100644 index 00000000000..7cfc6386b35 --- /dev/null +++ b/arch/arm/mach-ux500/board-u5500-bm.c @@ -0,0 +1,495 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U5500 board specific charger and battery initialization parameters. + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + */ + +#include <linux/power_supply.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500-bm.h> +#include "board-u5500-bm.h" + +#ifdef CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL +/* + * These are the defined batteries that uses a NTC and ID resistor placed + * inside of the battery pack. + * Note that the abx500_res_to_temp table must be strictly sorted by falling resistance + * values to work. + */ +static struct abx500_res_to_temp temp_tbl_type1[] = { + {-20, 67400}, + { 0, 49200}, + { 5, 44200}, + { 10, 39400}, + { 15, 35000}, + { 20, 31000}, + { 25, 27400}, + { 30, 24300}, + { 35, 21700}, + { 40, 19400}, + { 45, 17500}, + { 50, 15900}, + { 55, 14600}, + { 60, 13500}, + { 65, 12500}, + { 70, 11800}, + {100, 9200}, +}; + +static struct abx500_res_to_temp temp_tbl_type2[] = { + {-20, 180700}, + { 0, 160000}, + { 5, 152700}, + { 10, 144900}, + { 15, 136800}, + { 20, 128700}, + { 25, 121000}, + { 30, 113800}, + { 35, 107300}, + { 40, 101500}, + { 45, 96500}, + { 50, 92200}, + { 55, 88600}, + { 60, 85600}, + { 65, 83000}, + { 70, 80900}, + {100, 73900}, +}; + +static struct abx500_res_to_temp temp_tbl_A[] = { + {-5, 53407}, + { 0, 48594}, + { 5, 43804}, + {10, 39188}, + {15, 34870}, + {20, 30933}, + {25, 27422}, + {30, 24347}, + {35, 21694}, + {40, 19431}, + {45, 17517}, + {50, 15908}, + {55, 14561}, + {60, 13437}, + {65, 12500}, +}; + +static struct abx500_res_to_temp temp_tbl_B[] = { + {-5, 165418}, + { 0, 159024}, + { 5, 151921}, + {10, 144300}, + {15, 136424}, + {20, 128565}, + {25, 120978}, + {30, 113875}, + {35, 107397}, + {40, 101629}, + {45, 96592}, + {50, 92253}, + {55, 88569}, + {60, 85461}, + {65, 82869}, +}; + +static struct abx500_v_to_cap cap_tbl_type1[] = { + {4171, 100}, + {4114, 95}, + {4009, 83}, + {3947, 74}, + {3907, 67}, + {3863, 59}, + {3830, 56}, + {3813, 53}, + {3791, 46}, + {3771, 33}, + {3754, 25}, + {3735, 20}, + {3717, 17}, + {3681, 13}, + {3664, 8}, + {3651, 6}, + {3635, 5}, + {3560, 3}, + {3408, 1}, + {3247, 0}, +}; + +static struct abx500_v_to_cap cap_tbl_A[] = { + {4171, 100}, + {4114, 95}, + {4009, 83}, + {3947, 74}, + {3907, 67}, + {3863, 59}, + {3830, 56}, + {3813, 53}, + {3791, 46}, + {3771, 33}, + {3754, 25}, + {3735, 20}, + {3717, 17}, + {3681, 13}, + {3664, 8}, + {3651, 6}, + {3635, 5}, + {3560, 3}, + {3408, 1}, + {3247, 0}, +}; +static struct abx500_v_to_cap cap_tbl_B[] = { + {4161, 100}, + {4124, 98}, + {4044, 90}, + {4003, 85}, + {3966, 80}, + {3933, 75}, + {3888, 67}, + {3849, 60}, + {3813, 55}, + {3787, 47}, + {3772, 30}, + {3751, 25}, + {3718, 20}, + {3681, 16}, + {3660, 14}, + {3589, 10}, + {3546, 7}, + {3495, 4}, + {3404, 2}, + {3250, 0}, +}; +#endif +static struct abx500_v_to_cap cap_tbl[] = { + {4186, 100}, + {4163, 99}, + {4114, 95}, + {4068, 90}, + {3990, 80}, + {3926, 70}, + {3898, 65}, + {3866, 60}, + {3833, 55}, + {3812, 50}, + {3787, 40}, + {3768, 30}, + {3747, 25}, + {3730, 20}, + {3705, 15}, + {3699, 14}, + {3684, 12}, + {3672, 9}, + {3657, 7}, + {3638, 6}, + {3556, 4}, + {3424, 2}, + {3317, 1}, + {3094, 0}, +}; + +/* + * Note that the abx500_res_to_temp table must be strictly sorted by falling + * resistance values to work. + */ +static struct abx500_res_to_temp temp_tbl[] = { + {-5, 214834}, + { 0, 162943}, + { 5, 124820}, + {10, 96520}, + {15, 75306}, + {20, 59254}, + {25, 47000}, + {30, 37566}, + {35, 30245}, + {40, 24520}, + {45, 20010}, + {50, 16432}, + {55, 13576}, + {60, 11280}, + {65, 9425}, +}; + +static const struct abx500_battery_type bat_type[] = { + [BATTERY_UNKNOWN] = { + /* First element always represent the UNKNOWN battery */ + .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, + .resis_high = 0, + .resis_low = 0, + .battery_resistance = 300, + .charge_full_design = 612, + .nominal_voltage = 3700, + .termination_vol = 4050, + .termination_curr = 200, + .recharge_vol = 3990, + .normal_cur_lvl = 400, + .normal_vol_lvl = 4100, + .maint_a_cur_lvl = 400, + .maint_a_vol_lvl = 4050, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 400, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + +#ifdef CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 70000, + .resis_low = 8200, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_type1), + .r_to_t_tbl = temp_tbl_type1, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_type1), + .v_to_cap_tbl = cap_tbl_type1, + + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 165418, + .resis_low = 82869, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_B), + .r_to_t_tbl = temp_tbl_B, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_B), + .v_to_cap_tbl = cap_tbl_B, + }, +#else +/* + * These are the batteries that doesn't have an internal NTC resistor to measure + * its temperature. The temperature in this case is measure with a NTC placed + * near the battery but on the PCB. + */ + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 76000, + .resis_low = 53000, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 30000, + .resis_low = 10000, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 95000, + .resis_low = 76001, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, +#endif +}; + +static char *ab5500_charger_supplied_to[] = { + "abx500_chargalg", + "ab5500_fg", + "ab5500_btemp", +}; + +static char *ab5500_btemp_supplied_to[] = { + "abx500_chargalg", + "ab5500_fg", +}; + +static char *ab5500_fg_supplied_to[] = { + "abx500_chargalg", +}; + +static char *abx500_chargalg_supplied_to[] = { + "ab5500_fg", +}; + +struct abx500_charger_platform_data ab5500_charger_plat_data = { + .supplied_to = ab5500_charger_supplied_to, + .num_supplicants = ARRAY_SIZE(ab5500_charger_supplied_to), +}; + +struct abx500_btemp_platform_data ab5500_btemp_plat_data = { + .supplied_to = ab5500_btemp_supplied_to, + .num_supplicants = ARRAY_SIZE(ab5500_btemp_supplied_to), +}; + +struct abx500_fg_platform_data ab5500_fg_plat_data = { + .supplied_to = ab5500_fg_supplied_to, + .num_supplicants = ARRAY_SIZE(ab5500_fg_supplied_to), +}; + +struct abx500_chargalg_platform_data abx500_chargalg_plat_data = { + .supplied_to = abx500_chargalg_supplied_to, + .num_supplicants = ARRAY_SIZE(abx500_chargalg_supplied_to), +}; + +static const struct abx500_bm_capacity_levels cap_levels = { + .critical = 2, + .low = 10, + .normal = 70, + .high = 95, + .full = 100, +}; + +static const struct abx500_fg_parameters fg = { + .recovery_sleep_timer = 10, + .recovery_total_time = 100, + .init_timer = 1, + .init_discard_time = 5, + .init_total_time = 40, + .high_curr_time = 60, + .accu_charging = 30, + .accu_high_curr = 30, + .high_curr_threshold = 50, + .lowbat_threshold = 3100, +}; + +static const struct abx500_maxim_parameters maxi_params = { + .ena_maxi = true, + .chg_curr = 910, + .wait_cycles = 10, + .charger_curr_step = 100, +}; + +static const struct abx500_bm_charger_parameters chg = { + .usb_volt_max = 5500, + .usb_curr_max = 1500, + .ac_volt_max = 7500, + .ac_curr_max = 1500, +}; + +struct abx500_bm_data ab5500_bm_data = { + .temp_under = 3, + .temp_low = 8, + /* TODO: Need to verify the temp values */ + .temp_high = 155, + .temp_over = 160, + .main_safety_tmr_h = 4, + .usb_safety_tmr_h = 4, + .bkup_bat_v = 0x00, + .bkup_bat_i = 0x00, + .no_maintenance = true, +#ifdef CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL + .adc_therm = ABx500_ADC_THERM_BATCTRL, +#else + .adc_therm = ABx500_ADC_THERM_BATTEMP, +#endif + .chg_unknown_bat = false, + .enable_overshoot = false, + .fg_res = 20, + .cap_levels = &cap_levels, + .bat_type = bat_type, + .n_btypes = ARRAY_SIZE(bat_type), + .batt_id = 0, + .interval_charging = 5, + .interval_not_charging = 120, + .temp_hysteresis = 3, + .maxi = &maxi_params, + .chg_params = &chg, + .fg_params = &fg, +}; + +/* ab5500 energy management platform data */ +struct abx500_bm_plat_data abx500_bm_pt_data = { + .battery = &ab5500_bm_data, + .charger = &ab5500_charger_plat_data, + .btemp = &ab5500_btemp_plat_data, + .fg = &ab5500_fg_plat_data, + .chargalg = &abx500_chargalg_plat_data, +}; diff --git a/arch/arm/mach-ux500/board-u5500-bm.h b/arch/arm/mach-ux500/board-u5500-bm.h new file mode 100644 index 00000000000..a6346905911 --- /dev/null +++ b/arch/arm/mach-ux500/board-u5500-bm.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U5500 board specific charger and battery initialization parameters. + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + */ + +#ifndef __BOARD_U5500_BM_H +#define __BOARD_U5500_BM_H + +#include <linux/mfd/abx500/ab5500-bm.h> + +extern struct abx500_charger_platform_data ab5500_charger_plat_data; +extern struct abx500_btemp_platform_data ab5500_btemp_plat_data; +extern struct abx500_fg_platform_data ab5500_fg_plat_data; +extern struct abx500_chargalg_platform_data abx500_chargalg_plat_data; +extern struct abx500_bm_data ab5500_bm_data; +extern struct abx500_bm_plat_data abx500_bm_pt_data; + +#endif diff --git a/arch/arm/mach-ux500/board-u5500-cyttsp.c b/arch/arm/mach-ux500/board-u5500-cyttsp.c new file mode 100755 index 00000000000..f0f8192fade --- /dev/null +++ b/arch/arm/mach-ux500/board-u5500-cyttsp.c @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2011 ST-Ericsson SA + * Author: Avinash A <avinash.a@stericsson.com> for ST-Ericsson + * License terms:GNU General Public License (GPL) version 2 + */ +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/spi/spi.h> +#include <linux/gpio.h> +#include <linux/cyttsp.h> +#include <linux/delay.h> +#include <linux/amba/pl022.h> +#include <plat/pincfg.h> +#include <mach/hardware.h> + +#include "pins-db5500.h" +#include "board-u5500.h" + +/* cyttsp_gpio_board_init : configures the touch panel. */ +static int cyttsp_plat_init(int on) +{ + int ret; + + ret = gpio_direction_output(CYPRESS_SLAVE_SELECT_GPIO, 1); + if (ret < 0) { + pr_err("slave select gpio direction failed\n"); + gpio_free(CYPRESS_SLAVE_SELECT_GPIO); + return ret; + } + + return 0; +} + +static int cyttsp_wakeup(void) +{ + int ret; + + ret = gpio_request(CYPRESS_TOUCH_INT_PIN, "Wakeup_pin"); + if (ret < 0) { + pr_err("touch gpio failed\n"); + return ret; + } + ret = gpio_direction_output(CYPRESS_TOUCH_INT_PIN, 1); + if (ret < 0) { + pr_err("touch gpio direction failed\n"); + goto out; + } + gpio_set_value(CYPRESS_TOUCH_INT_PIN, 0); + gpio_set_value(CYPRESS_TOUCH_INT_PIN, 1); + /* + * To wake up the controller from sleep + * state the interrupt pin needs to be + * pulsed twice with a delay greater + * than 2 micro seconds. + */ + udelay(3); + gpio_set_value(CYPRESS_TOUCH_INT_PIN, 0); + gpio_set_value(CYPRESS_TOUCH_INT_PIN, 1); + ret = gpio_direction_input(CYPRESS_TOUCH_INT_PIN); + if (ret < 0) { + pr_err("touch gpio direction IN config failed\n"); + goto out; + } +out: + gpio_free(CYPRESS_TOUCH_INT_PIN); + return 0; +} +static struct cyttsp_platform_data cyttsp_spi_platdata = { + .maxx = 480, + .maxy = 854, + .flags = 0, + .gen = CY_GEN3, + .use_st = 0, + .use_mt = 1, + .use_trk_id = 0, + .use_hndshk = 0, + .use_sleep = 1, + .use_gestures = 0, + .use_load_file = 0, + .use_force_fw_update = 0, + .use_virtual_keys = 0, + /* activate up to 4 groups and set active distance */ + .gest_set = CY_GEST_GRP_NONE | CY_ACT_DIST, + /* change scn_type to enable finger and/or stylus detection */ + .scn_typ = 0xA5, /* autodetect finger+stylus; balanced mutual scan */ + .act_intrvl = CY_ACT_INTRVL_DFLT, /* Active refresh interval; ms */ + .tch_tmout = CY_TCH_TMOUT_DFLT, /* Active touch timeout; ms */ + .lp_intrvl = CY_LP_INTRVL_DFLT, /* Low power refresh interval; ms */ + .init = cyttsp_plat_init, + .mt_sync = input_mt_sync, + .wakeup = cyttsp_wakeup, + .name = CY_SPI_NAME, + .irq_gpio = CYPRESS_TOUCH_INT_PIN, + .rst_gpio = CYPRESS_TOUCH_RST_GPIO, +}; + +static void cyttsp_spi_cs_control(u32 command) +{ + if (command == SSP_CHIP_SELECT) + gpio_set_value(CYPRESS_SLAVE_SELECT_GPIO, 0); + else if (command == SSP_CHIP_DESELECT) + gpio_set_value(CYPRESS_SLAVE_SELECT_GPIO, 1); +} + +static struct pl022_config_chip cyttsp_ssp_config_chip = { + .com_mode = INTERRUPT_TRANSFER, + .iface = SSP_INTERFACE_MOTOROLA_SPI, + /* we can act as master only */ + .hierarchy = SSP_MASTER, + .slave_tx_disable = 0, + .rx_lev_trig = SSP_RX_1_OR_MORE_ELEM, + .tx_lev_trig = SSP_TX_16_OR_MORE_EMPTY_LOC, + .ctrl_len = SSP_BITS_16, + .wait_state = SSP_MWIRE_WAIT_ZERO, + .duplex = SSP_MICROWIRE_CHANNEL_FULL_DUPLEX, + .cs_control = cyttsp_spi_cs_control, +}; + +static struct spi_board_info cypress_spi_devices[] = { + { + .modalias = CY_SPI_NAME, + .controller_data = &cyttsp_ssp_config_chip, + .platform_data = &cyttsp_spi_platdata, + .max_speed_hz = 1000000, + .bus_num = 1, + .chip_select = 0, + .mode = SPI_MODE_0, + } +}; + +void u5500_cyttsp_init(void) +{ + int ret = 0; + + ret = gpio_request(CYPRESS_SLAVE_SELECT_GPIO, "slave_select_gpio"); + if (ret < 0) { + pr_err("slave select gpio failed\n"); + return; + } + if (cpu_is_u5500v2()) + cyttsp_spi_platdata.invert = true; + spi_register_board_info(cypress_spi_devices, + ARRAY_SIZE(cypress_spi_devices)); +} + diff --git a/arch/arm/mach-ux500/board-u5500-pins.c b/arch/arm/mach-ux500/board-u5500-pins.c new file mode 100644 index 00000000000..4635f240815 --- /dev/null +++ b/arch/arm/mach-ux500/board-u5500-pins.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/gpio.h> +#include <linux/gpio/nomadik.h> +#include <plat/pincfg.h> + +#include "pins-db5500.h" +#include "pins.h" + +static pin_cfg_t u5500_pins_default[] = { + /* MSP */ + GPIO32_MSP0_TCK | PIN_INPUT_PULLDOWN, + GPIO33_MSP0_TFS | PIN_INPUT_PULLDOWN, + GPIO34_MSP0_TXD | PIN_INPUT_PULLDOWN, + GPIO35_MSP0_RXD | PIN_INPUT_PULLDOWN, + GPIO96_MSP1_TCK | PIN_INPUT_PULLDOWN, + GPIO97_MSP1_TFS | PIN_INPUT_PULLDOWN, + GPIO98_MSP1_TXD | PIN_INPUT_PULLDOWN, + GPIO99_MSP1_RXD | PIN_INPUT_PULLDOWN, + GPIO220_MSP2_TCK | PIN_OUTPUT_LOW, + GPIO221_MSP2_TFS | PIN_OUTPUT_LOW, + GPIO222_MSP2_TXD | PIN_OUTPUT_LOW, + + /* DISPLAY_ENABLE */ + GPIO226_GPIO | PIN_OUTPUT_LOW, + + /* Backlight Enable */ + GPIO224_GPIO | PIN_OUTPUT_HIGH, + + /* UART0 */ + GPIO28_U0_TXD | PIN_OUTPUT_HIGH, + GPIO29_U0_RXD | PIN_INPUT_PULLUP, + + /* UART3 */ + GPIO165_U3_RXD | PIN_INPUT_PULLUP, + GPIO166_U3_TXD | PIN_OUTPUT_HIGH, + GPIO167_U3_RTSn | PIN_OUTPUT_HIGH, + GPIO168_U3_CTSn | PIN_INPUT_PULLUP, + + /* AB5500 */ + GPIO78_IRQn | PIN_SLPM_NOCHANGE, + GPIO100_I2C0_SCL | PIN_INPUT_PULLUP | PIN_SLPM_NOCHANGE, + GPIO101_I2C0_SDA | PIN_SLPM_NOCHANGE, + + /* TOUCH_IRQ */ + GPIO179_GPIO | PIN_INPUT_PULLUP, + + /* SD-CARD detect/levelshifter pins */ + GPIO180_GPIO | PIN_INPUT_PULLUP, + GPIO227_GPIO, + GPIO185_GPIO, + + /* Display & HDMI HW sync */ + GPIO204_LCD_VSI1 | PIN_INPUT_PULLUP, + + /* Camera & MMIO XshutDown*/ + GPIO1_GPIO | PIN_OUTPUT_LOW, + GPIO2_GPIO | PIN_OUTPUT_LOW, + + /* USB chip select */ + GPIO76_GPIO | PIN_OUTPUT_LOW, + + GPIO202_ACCU0_RXD | PIN_INPUT_PULLUP | PIN_SLPM_NOCHANGE, + GPIO203_ACCU0_TXD | PIN_OUTPUT_HIGH | PIN_SLPM_NOCHANGE, + + /* Board Id Identification B5500 or S5500 */ + GPIO0_GPIO | PIN_INPUT_PULLUP, + +}; + +static UX500_PINS(db5500_kp_pins, + /* Keypad */ + GPIO128_KP_I0 | PIN_INPUT_PULLUP | PIN_SLPM_OUTPUT_LOW, + GPIO130_KP_I1 | PIN_INPUT_PULLUP | PIN_SLPM_INPUT_PULLUP, + GPIO132_KP_I2 | PIN_INPUT_PULLUP | PIN_SLPM_INPUT_PULLUP, + GPIO134_KP_I3 | PIN_INPUT_PULLUP | PIN_SLPM_INPUT_PULLUP, + GPIO137_KP_O4 | PIN_INPUT_PULLUP | PIN_SLPM_OUTPUT_LOW, + GPIO139_KP_O5 | PIN_INPUT_PULLUP | PIN_SLPM_OUTPUT_LOW, +); + +static UX500_PINS(db5500_pins_sdi0, + /* SDI0 (eMMC) */ + GPIO5_MC0_DAT0 | PIN_INPUT_PULLUP, + GPIO6_MC0_DAT1 | PIN_INPUT_PULLUP, + GPIO7_MC0_DAT2 | PIN_INPUT_PULLUP, + GPIO8_MC0_DAT3 | PIN_INPUT_PULLUP, + GPIO9_MC0_DAT4 | PIN_INPUT_PULLUP, + GPIO10_MC0_DAT5 | PIN_INPUT_PULLUP, + GPIO11_MC0_DAT6 | PIN_INPUT_PULLUP, + GPIO12_MC0_DAT7 | PIN_INPUT_PULLUP, + GPIO13_MC0_CMD | PIN_INPUT_PULLUP, + GPIO14_MC0_CLK | PIN_OUTPUT_LOW, +); + +static UX500_PINS(db5500_pins_sdi1, + /* SDI1 (SD-CARD) */ + GPIO191_MC1_DAT0 | PIN_INPUT_PULLUP, + GPIO192_MC1_DAT1 | PIN_INPUT_PULLUP, + GPIO193_MC1_DAT2 | PIN_INPUT_PULLUP, + GPIO194_MC1_DAT3 | PIN_INPUT_PULLUP, + GPIO195_MC1_CLK | PIN_OUTPUT_LOW, + GPIO196_MC1_CMD | PIN_INPUT_PULLUP, + GPIO197_MC1_CMDDIR | PIN_OUTPUT_HIGH, + GPIO198_MC1_FBCLK | PIN_INPUT_NOPULL, + GPIO199_MC1_DAT0DIR | PIN_OUTPUT_HIGH, +); + +static UX500_PINS(db5500_pins_sdi2, + /* SDI2 (eMMC) */ + GPIO16_MC2_CMD | PIN_INPUT_PULLUP, + GPIO17_MC2_CLK | PIN_OUTPUT_LOW, + GPIO23_MC2_DAT0 | PIN_INPUT_PULLUP, + GPIO19_MC2_DAT1 | PIN_INPUT_PULLUP, + GPIO24_MC2_DAT2 | PIN_INPUT_PULLUP, + GPIO20_MC2_DAT3 | PIN_INPUT_PULLUP, + GPIO25_MC2_DAT4 | PIN_INPUT_PULLUP, + GPIO21_MC2_DAT5 | PIN_INPUT_PULLUP, + GPIO26_MC2_DAT6 | PIN_INPUT_PULLUP, + GPIO22_MC2_DAT7 | PIN_INPUT_PULLUP +); + +static UX500_PINS(db5500_pins_sdi3, + /* SDI3 (SDIO) */ + GPIO171_MC3_DAT0 | PIN_INPUT_PULLUP | PIN_LOWEMI_ENABLED, + GPIO172_MC3_DAT1 | PIN_INPUT_PULLUP | PIN_LOWEMI_ENABLED, + GPIO173_MC3_DAT2 | PIN_INPUT_PULLUP | PIN_LOWEMI_ENABLED, + GPIO174_MC3_DAT3 | PIN_INPUT_PULLUP | PIN_LOWEMI_ENABLED, + GPIO175_MC3_CMD | PIN_INPUT_PULLUP | PIN_LOWEMI_ENABLED, + GPIO176_MC3_CLK | PIN_OUTPUT_LOW, +); + +static UX500_PINS(u5500_pins_i2c1, + GPIO3_I2C1_SCL, + GPIO4_I2C1_SDA, +); + +static UX500_PINS(u5500_pins_i2c2, + GPIO218_I2C2_SCL, + GPIO219_I2C2_SDA, +); + +static UX500_PINS(u5500_pins_i2c3, + GPIO177_I2C3_SCL, + GPIO178_I2C3_SDA, +); + +static UX500_PINS(u5500_pins_spi3, + GPIO186_GPIO | PIN_OUTPUT_HIGH, + GPIO188_SPI3_RXD | PIN_INPUT_PULLDOWN, + GPIO189_SPI3_TXD | PIN_OUTPUT_LOW, + GPIO190_SPI3_CLK | PIN_OUTPUT_LOW, +); + +/* USB */ +static UX500_PINS(u5500_pins_usb, + GPIO74_USB_NXT, + GPIO72_USB_STP | PIN_OUTPUT_HIGH, + GPIO75_USB_XCLK, + GPIO73_USB_DIR, + GPIO71_USB_DAT7, + GPIO70_USB_DAT6, + GPIO69_USB_DAT5, + GPIO68_USB_DAT4, + GPIO67_USB_DAT3, + GPIO66_USB_DAT2, + GPIO65_USB_DAT1, + GPIO64_USB_DAT0, +); + +static struct ux500_pin_lookup u5500_pins[] = { + PIN_LOOKUP("nmk-i2c.1", &u5500_pins_i2c1), + PIN_LOOKUP("nmk-i2c.2", &u5500_pins_i2c2), + PIN_LOOKUP("nmk-i2c.3", &u5500_pins_i2c3), + PIN_LOOKUP("spi3", &u5500_pins_spi3), + PIN_LOOKUP("db5500_kp", &db5500_kp_pins), + PIN_LOOKUP("ab5500-usb.0", &u5500_pins_usb), + PIN_LOOKUP("sdi0", &db5500_pins_sdi0), + PIN_LOOKUP("sdi1", &db5500_pins_sdi1), + PIN_LOOKUP("sdi2", &db5500_pins_sdi2), + PIN_LOOKUP("sdi3", &db5500_pins_sdi3), +}; + +void __init u5500_pins_init(void) +{ + nmk_config_pins(u5500_pins_default, ARRAY_SIZE(u5500_pins_default)); + ux500_pins_add(u5500_pins, ARRAY_SIZE(u5500_pins)); +} diff --git a/arch/arm/mach-ux500/board-u5500-regulators.c b/arch/arm/mach-ux500/board-u5500-regulators.c new file mode 100644 index 00000000000..66e8f3593d5 --- /dev/null +++ b/arch/arm/mach-ux500/board-u5500-regulators.c @@ -0,0 +1,182 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/regulator/fixed.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/ab5500.h> + +#include "regulator-u5500.h" +#include "board-u5500.h" + +/* + * AB5500 + */ + +static struct regulator_consumer_supply ab5500_ldo_g_consumers[] = { + REGULATOR_SUPPLY("vmmc", "sdi1"), +}; + +static struct regulator_consumer_supply ab5500_ldo_h_consumers[] = { + REGULATOR_SUPPLY("vddi", "mcde_disp_sony_acx424akp.0"), + REGULATOR_SUPPLY("vdd", "1-004b"), /* Synaptics */ + REGULATOR_SUPPLY("vin", "2-0036"), /* LM3530 */ + REGULATOR_SUPPLY("vcpin", "spi1.0"), + REGULATOR_SUPPLY("v-ana", "mmio_camera"), +}; + +static struct regulator_consumer_supply ab5500_ldo_k_consumers[] = { + REGULATOR_SUPPLY("vdd", "lsm303dlh.0"), + REGULATOR_SUPPLY("vdd", "lsm303dlh.1"), + REGULATOR_SUPPLY("v-mmio-camera", "mmio_camera"), +}; + +static struct regulator_consumer_supply ab5500_ldo_l_consumers[] = { + REGULATOR_SUPPLY("vmmc", "sdi0"), + REGULATOR_SUPPLY("vmmc", "sdi2"), +}; + +static struct regulator_consumer_supply ab5500_ldo_vdigmic_consumers[] = { +}; + +static struct regulator_consumer_supply ab5500_ldo_sim_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.5"), +}; + +static struct regulator_consumer_supply ab5500_bias2_consumers[] = { + REGULATOR_SUPPLY("v-amic", NULL), +}; + +static struct regulator_init_data +ab5500_regulator_init_data[AB5500_NUM_REGULATORS] = { + /* SD Card */ + [AB5500_LDO_G] = { + .constraints = { + .min_uV = 1200000, + .max_uV = 2910000, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, + }, + .consumer_supplies = ab5500_ldo_g_consumers, + .num_consumer_supplies = ARRAY_SIZE(ab5500_ldo_g_consumers), + }, + /* Display */ + [AB5500_LDO_H] = { + .constraints = { + .min_uV = 2790000, + .max_uV = 2790000, + .apply_uV = 1, + .boot_on = 1, /* display on during boot */ + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = ab5500_ldo_h_consumers, + .num_consumer_supplies = ARRAY_SIZE(ab5500_ldo_h_consumers), + }, + /* Camera */ + [AB5500_LDO_K] = { + .constraints = { + .min_uV = 2790000, + .max_uV = 2790000, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = ab5500_ldo_k_consumers, + .num_consumer_supplies = ARRAY_SIZE(ab5500_ldo_k_consumers), + }, + /* External eMMC */ + [AB5500_LDO_L] = { + .constraints = { + .min_uV = 1200000, + .max_uV = 2910000, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS | + REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_NORMAL | + REGULATOR_MODE_IDLE, + }, + .consumer_supplies = ab5500_ldo_l_consumers, + .num_consumer_supplies = ARRAY_SIZE(ab5500_ldo_l_consumers), + }, + [AB5500_LDO_VDIGMIC] = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = ab5500_ldo_vdigmic_consumers, + .num_consumer_supplies = + ARRAY_SIZE(ab5500_ldo_vdigmic_consumers), + }, + [AB5500_LDO_SIM] = { + .constraints = { + .boot_on = 1, + .always_on = 1, + .min_uV = 2900000, + .max_uV = 2900000, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | + REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = ab5500_ldo_sim_consumers, + .num_consumer_supplies = ARRAY_SIZE(ab5500_ldo_sim_consumers), + }, + [AB5500_BIAS2] = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = ab5500_bias2_consumers, + .num_consumer_supplies = ARRAY_SIZE(ab5500_bias2_consumers), + }, +}; + +struct ab5500_regulator_platform_data u5500_ab5500_regulator_data = { + .regulator = ab5500_regulator_init_data, + .num_regulator = ARRAY_SIZE(ab5500_regulator_init_data), +}; + + +static void __init u5500_regulators_init_debug(void) +{ + const char data[] = "debug"; + int i; + + for (i = 0; i < 6; i++) + platform_device_register_data(NULL, "reg-virt-consumer", i, + data, sizeof(data)); +} + +static struct regulator_consumer_supply u5500_vio_consumers[] = { + REGULATOR_SUPPLY("gbf_1v8", "cg2900-uart.0"), +}; + +static struct regulator_init_data u5500_vio_init_data = { + .constraints.always_on = 1, + .consumer_supplies = u5500_vio_consumers, + .num_consumer_supplies = ARRAY_SIZE(u5500_vio_consumers), +}; + +static struct fixed_voltage_config u5500_vio_pdata __initdata = { + .supply_name = "vio_1v8", + .microvolts = 1800000, + .init_data = &u5500_vio_init_data, + .gpio = -EINVAL, +}; + +void __init u5500_regulators_init(void) +{ + u5500_regulators_init_debug(); + + platform_device_register_data(NULL, "reg-fixed-voltage", -1, + &u5500_vio_pdata, + sizeof(u5500_vio_pdata)); + + regulator_has_full_constraints(); +} diff --git a/arch/arm/mach-ux500/board-u5500-sdi.c b/arch/arm/mach-ux500/board-u5500-sdi.c index 739fb4c5b16..475e41e0628 100644 --- a/arch/arm/mach-ux500/board-u5500-sdi.c +++ b/arch/arm/mach-ux500/board-u5500-sdi.c @@ -5,34 +5,28 @@ * License terms: GNU General Public License (GPL) version 2 */ +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/amba/bus.h> #include <linux/amba/mmci.h> #include <linux/mmc/host.h> -#include <linux/gpio.h> +#include <linux/platform_device.h> +#include <linux/delay.h> -#include <plat/pincfg.h> -#include <mach/db5500-regs.h> +#include <asm/mach-types.h> #include <plat/ste_dma40.h> +#include <mach/devices.h> +#include <mach/hardware.h> +#include <mach/ste-dma40-db5500.h> -#include "pins-db5500.h" #include "devices-db5500.h" -#include "ste-dma40-db5500.h" - -static pin_cfg_t u5500_sdi_pins[] = { - /* SDI0 (POP eMMC) */ - GPIO5_MC0_DAT0 | PIN_DIR_INPUT | PIN_PULL_UP, - GPIO6_MC0_DAT1 | PIN_DIR_INPUT | PIN_PULL_UP, - GPIO7_MC0_DAT2 | PIN_DIR_INPUT | PIN_PULL_UP, - GPIO8_MC0_DAT3 | PIN_DIR_INPUT | PIN_PULL_UP, - GPIO9_MC0_DAT4 | PIN_DIR_INPUT | PIN_PULL_UP, - GPIO10_MC0_DAT5 | PIN_DIR_INPUT | PIN_PULL_UP, - GPIO11_MC0_DAT6 | PIN_DIR_INPUT | PIN_PULL_UP, - GPIO12_MC0_DAT7 | PIN_DIR_INPUT | PIN_PULL_UP, - GPIO13_MC0_CMD | PIN_DIR_INPUT | PIN_PULL_UP, - GPIO14_MC0_CLK | PIN_DIR_OUTPUT | PIN_VAL_LOW, -}; +#include "board-u5500.h" +/* + * SDI 0 (eMMC) + */ #ifdef CONFIG_STE_DMA40 -struct stedma40_chan_cfg u5500_sdi0_dma_cfg_rx = { +static struct stedma40_chan_cfg sdi0_dma_cfg_rx = { .mode = STEDMA40_MODE_LOGICAL, .dir = STEDMA40_PERIPH_TO_MEM, .src_dev_type = DB5500_DMA_DEV24_SDMMC0_RX, @@ -41,7 +35,7 @@ struct stedma40_chan_cfg u5500_sdi0_dma_cfg_rx = { .dst_info.data_width = STEDMA40_WORD_WIDTH, }; -static struct stedma40_chan_cfg u5500_sdi0_dma_cfg_tx = { +static struct stedma40_chan_cfg sdi0_dma_cfg_tx = { .mode = STEDMA40_MODE_LOGICAL, .dir = STEDMA40_MEM_TO_PERIPH, .src_dev_type = STEDMA40_DEV_SRC_MEMORY, @@ -61,14 +55,208 @@ static struct mmci_platform_data u5500_sdi0_data = { .gpio_wp = -1, #ifdef CONFIG_STE_DMA40 .dma_filter = stedma40_filter, - .dma_rx_param = &u5500_sdi0_dma_cfg_rx, - .dma_tx_param = &u5500_sdi0_dma_cfg_tx, + .dma_rx_param = &sdi0_dma_cfg_rx, + .dma_tx_param = &sdi0_dma_cfg_tx, +#endif +}; + +/* + * SDI 1 (MicroSD slot) + */ + +static int u5500_sdi1_ios_handler(struct device *dev, struct mmc_ios *ios) +{ + switch (ios->power_mode) { + case MMC_POWER_UP: + case MMC_POWER_ON: + /* + * Level shifter voltage should depend on vdd to when deciding + * on either 1.8V or 2.9V. Once the decision has been made the + * level shifter must be disabled and re-enabled with a changed + * select signal in order to switch the voltage. Since there is + * no framework support yet for indicating 1.8V in vdd, use the + * default 2.9V. + */ + gpio_set_value_cansleep(GPIO_MMC_CARD_CTRL, 1); + udelay(100); + break; + case MMC_POWER_OFF: + gpio_set_value_cansleep(GPIO_MMC_CARD_CTRL, 0); + break; + } + + return 0; +} + +#ifdef SD_WORKAROUND +static struct stedma40_chan_cfg sdi1_dma_cfg_rx = { + .mode = STEDMA40_MODE_LOGICAL, + .dir = STEDMA40_PERIPH_TO_MEM, + .src_dev_type = DB5500_DMA_DEV25_SDMMC1_RX, + .dst_dev_type = STEDMA40_DEV_DST_MEMORY, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, +}; + +static struct stedma40_chan_cfg sdi1_dma_cfg_tx = { + .mode = STEDMA40_MODE_LOGICAL, + .dir = STEDMA40_MEM_TO_PERIPH, + .src_dev_type = STEDMA40_DEV_SRC_MEMORY, + .dst_dev_type = DB5500_DMA_DEV25_SDMMC1_TX, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, +}; +#endif +static struct mmci_platform_data u5500_sdi1_data = { + .ios_handler = u5500_sdi1_ios_handler, + .ocr_mask = MMC_VDD_29_30, + .f_max = 50000000, + .capabilities = MMC_CAP_4_BIT_DATA | + MMC_CAP_SD_HIGHSPEED | + MMC_CAP_MMC_HIGHSPEED, + .gpio_cd = GPIO_SDMMC_CD, + .gpio_wp = -1, + .cd_invert = true, +#ifdef SD_WORKAROUND +#ifdef CONFIG_STE_DMA40 + .dma_filter = stedma40_filter, + .dma_rx_param = &sdi1_dma_cfg_rx, + .dma_tx_param = &sdi1_dma_cfg_tx, +#endif +#endif +}; + +/* + * SDI2 (EMMC2) + */ + +static struct stedma40_chan_cfg sdi2_dma_cfg_rx = { + .mode = STEDMA40_MODE_LOGICAL, + .dir = STEDMA40_PERIPH_TO_MEM, + .src_dev_type = DB5500_DMA_DEV26_SDMMC2_RX, + .dst_dev_type = STEDMA40_DEV_DST_MEMORY, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, +}; + +static struct stedma40_chan_cfg sdi2_dma_cfg_tx = { + .mode = STEDMA40_MODE_LOGICAL, + .dir = STEDMA40_MEM_TO_PERIPH, + .src_dev_type = STEDMA40_DEV_SRC_MEMORY, + .dst_dev_type = DB5500_DMA_DEV26_SDMMC2_TX, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, +}; + +static struct mmci_platform_data u5500_sdi2_data = { + .ocr_mask = MMC_VDD_165_195, + .f_max = 50000000, + .capabilities = MMC_CAP_4_BIT_DATA | + MMC_CAP_8_BIT_DATA | + MMC_CAP_MMC_HIGHSPEED, + .gpio_cd = -1, + .gpio_wp = -1, +#ifdef CONFIG_STE_DMA40 + .dma_filter = stedma40_filter, + .dma_rx_param = &sdi2_dma_cfg_rx, + .dma_tx_param = &sdi2_dma_cfg_tx, +#endif +}; + +/* + * SDI 3 (SDIO WLAN) + */ +#ifdef SDIO_DMA_ON +#ifdef CONFIG_STE_DMA40 +static struct stedma40_chan_cfg sdi3_dma_cfg_rx = { + .mode = STEDMA40_MODE_LOGICAL, + .dir = STEDMA40_PERIPH_TO_MEM, + .src_dev_type = DB5500_DMA_DEV27_SDMMC3_RX, + .dst_dev_type = STEDMA40_DEV_DST_MEMORY, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, +}; + +static struct stedma40_chan_cfg sdi3_dma_cfg_tx = { + .mode = STEDMA40_MODE_LOGICAL, + .dir = STEDMA40_MEM_TO_PERIPH, + .src_dev_type = STEDMA40_DEV_SRC_MEMORY, + .dst_dev_type = DB5500_DMA_DEV27_SDMMC3_TX, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, +}; +#endif +#endif + +static struct mmci_platform_data u5500_sdi3_data = { + .ocr_mask = MMC_VDD_29_30, + .f_max = 50000000, + .capabilities = MMC_CAP_4_BIT_DATA, + .gpio_cd = -1, + .gpio_wp = -1, +#ifdef SDIO_DMA_ON +#ifdef CONFIG_STE_DMA40 + .dma_filter = stedma40_filter, + .dma_rx_param = &sdi3_dma_cfg_rx, + .dma_tx_param = &sdi3_dma_cfg_tx, +#endif #endif }; +static void sdi1_configure(void) +{ + int pin[2]; + int ret; + + /* Level-shifter GPIOs */ + pin[0] = GPIO_MMC_CARD_CTRL; + pin[1] = GPIO_MMC_CARD_VSEL; + + ret = gpio_request(pin[0], "MMC_CARD_CTRL"); + if (!ret) + ret = gpio_request(pin[1], "MMC_CARD_VSEL"); + + if (ret) { + pr_warning("unable to config sdi0 gpios for level shifter.\n"); + return; + } + /* Select the default 2.9V and eanble level shifter */ + gpio_direction_output(pin[0], 1); + gpio_direction_output(pin[1], 0); +} + +#define SDI_PID_V1 0x00480180 +#define SDI_PID_V2 0x10480180 +#define BACKUPRAM_ROM_DEBUG_ADDR 0xFFC +#define MMC_BLOCK_ID 0x20 void __init u5500_sdi_init(void) { - nmk_config_pins(u5500_sdi_pins, ARRAY_SIZE(u5500_sdi_pins)); + u32 periphid = 0; + int mmc_blk = 0; + + if (cpu_is_u5500v1()) + periphid = SDI_PID_V1; + else + periphid = SDI_PID_V2; + + if (cpu_is_u5500v2()) + /* + * Fix me in 5500 v2.1 + * Dynamic detection of booting device by reading + * ROM debug register from BACKUP RAM and register the + * corresponding EMMC. + * This is done due to wrong configuration of MMC0 clock + * in ROM code for u5500 v2. + */ + mmc_blk = readl(__io_address(U5500_BACKUPRAM1_BASE) + + BACKUPRAM_ROM_DEBUG_ADDR); + + if (mmc_blk & MMC_BLOCK_ID) + db5500_add_sdi2(&u5500_sdi2_data, periphid); + else + db5500_add_sdi0(&u5500_sdi0_data, periphid); - db5500_add_sdi0(&u5500_sdi0_data); + sdi1_configure(); + db5500_add_sdi1(&u5500_sdi1_data, periphid); + db5500_add_sdi3(&u5500_sdi3_data, periphid); } diff --git a/arch/arm/mach-ux500/board-u5500.c b/arch/arm/mach-ux500/board-u5500.c index e58f0f56242..7752f22d95e 100644 --- a/arch/arm/mach-ux500/board-u5500.c +++ b/arch/arm/mach-ux500/board-u5500.c @@ -1,16 +1,34 @@ /* * Copyright (C) ST-Ericsson SA 2010 * - * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson * License terms: GNU General Public License (GPL) version 2 */ #include <linux/init.h> #include <linux/platform_device.h> #include <linux/amba/bus.h> +#include <linux/amba/pl022.h> #include <linux/gpio.h> #include <linux/irq.h> #include <linux/i2c.h> +#ifdef CONFIG_STM_I2S +#include <linux/i2s/i2s.h> +#endif +#include <linux/mfd/abx500.h> +#include <linux/led-lm3530.h> +#include <../drivers/staging/ste_rmi4/synaptics_i2c_rmi4.h> +#include <linux/input/matrix_keypad.h> +#ifdef CONFIG_SENSORS_LSM303DLH +#include <linux/lsm303dlh.h> +#endif +#include <linux/leds-ab5500.h> +#ifdef CONFIG_TOUCHSCREEN_CYTTSP_SPI +#include <linux/cyttsp.h> +#endif + +#ifdef CONFIG_AV8100 +#include <video/av8100.h> +#endif #include <asm/mach/arch.h> #include <asm/mach-types.h> @@ -19,33 +37,130 @@ #include <plat/i2c.h> #include <mach/hardware.h> +#include <mach/ste-dma40-db5500.h> +#ifdef CONFIG_STM_I2S +#include <mach/msp.h> +#endif #include <mach/devices.h> #include <mach/setup.h> +#include <mach/db5500-keypad.h> +#include <mach/crypto-ux500.h> +#include <mach/usb.h> +#include <mach/abx500-accdet.h> #include "pins-db5500.h" +#include "pins.h" #include "devices-db5500.h" -#include <linux/led-lm3530.h> +#include "board-u5500.h" +#include "board-u5500-bm.h" +#include "board-u5500-wlan.h" +#include "board-ux500-usb.h" + +#ifdef CONFIG_SENSORS_LSM303DLH +/* + * LSM303DLH + */ + +static struct lsm303dlh_platform_data __initdata lsm303dlh_pdata = { + .name_a = "lsm303dlh.0", + .name_m = "lsm303dlh.1", + .axis_map_x = 1, + .axis_map_y = 0, + .axis_map_z = 2, + .negative_x = 0, + .negative_y = 0, + .negative_z = 1, +}; +#endif + +/* + * Touchscreen + */ +static struct synaptics_rmi4_platform_data rmi4_i2c_platformdata = { + .irq_number = NOMADIK_GPIO_TO_IRQ(179), + .irq_type = (IRQF_TRIGGER_FALLING | IRQF_SHARED), +#if defined(CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_ROTATION_ANGLE) && \ + CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_ROTATION_ANGLE == 270 + .x_flip = true, + .y_flip = false, +#else + .x_flip = false, + .y_flip = true, +#endif + .regulator_en = true, +}; + +static struct av8100_platform_data av8100_plat_data = { + .irq = NOMADIK_GPIO_TO_IRQ(223), + .reset = 225, + .alt_powerupseq = true, + .mclk_freq = 1, /* MCLK_RNG_22_27 */ +}; /* - * GPIO + * leds LM3530 */ +static struct lm3530_platform_data u5500_als_platform_data = { + .mode = LM3530_BL_MODE_MANUAL, + .als_input_mode = LM3530_INPUT_ALS1, + .max_current = LM3530_FS_CURR_26mA, + .pwm_pol_hi = true, + .als_avrg_time = LM3530_ALS_AVRG_TIME_512ms, + .brt_ramp_law = 1, /* Linear */ + .brt_ramp_fall = LM3530_RAMP_TIME_8s, + .brt_ramp_rise = LM3530_RAMP_TIME_8s, + .als1_resistor_sel = LM3530_ALS_IMPD_13_53kOhm, + .als2_resistor_sel = LM3530_ALS_IMPD_Z, + .als_vmin = 730, /* mV */ + .als_vmax = 1020, /* mV */ + .brt_val = 0x7F, /* Max brightness */ +}; -static pin_cfg_t u5500_pins[] = { - /* I2C */ - GPIO218_I2C2_SCL | PIN_INPUT_PULLUP, - GPIO219_I2C2_SDA | PIN_INPUT_PULLUP, - /* DISPLAY_ENABLE */ - GPIO226_GPIO | PIN_OUTPUT_LOW, +/* leds-ab5500 */ +static struct ab5500_hvleds_platform_data ab5500_hvleds_data = { + .hw_fade = false, + .leds = { + [0] = { + .name = "red", + .led_on = true, + .led_id = 0, + .fade_hi = 255, + .fade_lo = 0, + .max_current = 10, /* wrong value may damage h/w */ + }, + [1] = { + .name = "green", + .led_on = true, + .led_id = 1, + .fade_hi = 255, + .fade_lo = 0, + .max_current = 10, /* wrong value may damage h/w */ + }, + [2] { + .name = "blue", + .led_on = true, + .led_id = 2, + .fade_hi = 255, + .fade_lo = 0, + .max_current = 10, /* wrong value may damage h/w */ + }, + }, +}; - /* Backlight Enbale */ - GPIO224_GPIO | PIN_OUTPUT_HIGH, +static struct ab5500_ponkey_platform_data ab5500_ponkey_data = { + /* + * Shutdown time in secs. Can be set + * to 10sec, 5sec and 0sec(disabled) + */ + .shutdown_secs = 10, }; + /* * I2C */ -#define U5500_I2C_CONTROLLER(id, _slsu, _tft, _rft, clk, _sm) \ +#define U5500_I2C_CONTROLLER(id, _slsu, _tft, _rft, clk, t_out, _sm) \ static struct nmk_i2c_controller u5500_i2c##id##_data = { \ /* \ * slave data setup time, which is \ @@ -60,33 +175,58 @@ static struct nmk_i2c_controller u5500_i2c##id##_data = { \ .rft = _rft, \ /* std. mode operation */ \ .clk_freq = clk, \ + /* Slave response timeout(ms) */\ + .timeout = t_out, \ .sm = _sm, \ } + /* - * The board uses TODO <3> i2c controllers, initialize all of + * The board uses 3 i2c controllers, initialize all of * them with slave data setup time of 250 ns, * Tx & Rx FIFO threshold values as 1 and standard * mode of operation */ -U5500_I2C_CONTROLLER(2, 0xe, 1, 1, 400000, I2C_FREQ_MODE_FAST); +U5500_I2C_CONTROLLER(1, 0xe, 1, 10, 400000, 200, I2C_FREQ_MODE_FAST); +U5500_I2C_CONTROLLER(2, 0xe, 1, 10, 400000, 200, I2C_FREQ_MODE_FAST); +U5500_I2C_CONTROLLER(3, 0xe, 1, 10, 400000, 200, I2C_FREQ_MODE_FAST); -static struct lm3530_platform_data u5500_als_platform_data = { - .mode = LM3530_BL_MODE_MANUAL, - .als_input_mode = LM3530_INPUT_ALS1, - .max_current = LM3530_FS_CURR_26mA, - .pwm_pol_hi = true, - .als_avrg_time = LM3530_ALS_AVRG_TIME_512ms, - .brt_ramp_law = 1, /* Linear */ - .brt_ramp_fall = LM3530_RAMP_TIME_8s, - .brt_ramp_rise = LM3530_RAMP_TIME_8s, - .als1_resistor_sel = LM3530_ALS_IMPD_13_53kOhm, - .als2_resistor_sel = LM3530_ALS_IMPD_Z, - .als_vmin = 730, /* mV */ - .als_vmax = 1020, /* mV */ - .brt_val = 0x7F, /* Max brightness */ +static struct i2c_board_info __initdata u5500_i2c1_devices[] = { + { + I2C_BOARD_INFO("synaptics_rmi4_i2c", 0x4B), + .platform_data = &rmi4_i2c_platformdata, + }, +}; + +static struct i2c_board_info __initdata u5500v1_i2c2_sensor_devices[] = { +#ifdef CONFIG_SENSORS_LSM303DLH + { + /* LSM303DLH Accelerometer */ + I2C_BOARD_INFO("lsm303dlh_a", 0x19), + .platform_data = &lsm303dlh_pdata, + }, + { + /* LSM303DLH Magnetometer */ + I2C_BOARD_INFO("lsm303dlh_m", 0x1E), + .platform_data = &lsm303dlh_pdata, + }, +#endif }; +static struct i2c_board_info __initdata u5500v2_i2c2_sensor_devices[] = { +#ifdef CONFIG_SENSORS_LSM303DLH + { + /* LSM303DLHC Accelerometer */ + I2C_BOARD_INFO("lsm303dlhc_a", 0x19), + .platform_data = &lsm303dlh_pdata, + }, + { + /* LSM303DLH Magnetometer */ + I2C_BOARD_INFO("lsm303dlh_m", 0x1E), + .platform_data = &lsm303dlh_pdata, + }, +#endif +}; static struct i2c_board_info __initdata u5500_i2c2_devices[] = { { @@ -94,27 +234,431 @@ static struct i2c_board_info __initdata u5500_i2c2_devices[] = { I2C_BOARD_INFO("lm3530-led", 0x36), .platform_data = &u5500_als_platform_data, }, + { + I2C_BOARD_INFO("av8100", 0x70), + .platform_data = &av8100_plat_data, + }, +}; + +/* + * Keypad + */ + +#define ROW_PIN_I0 128 +#define ROW_PIN_I1 130 +#define ROW_PIN_I2 132 +#define ROW_PIN_I3 134 +#define COL_PIN_O4 137 +#define COL_PIN_O5 139 + +static int db5500_kp_rows[] = { + ROW_PIN_I0, ROW_PIN_I1, ROW_PIN_I2, ROW_PIN_I3, +}; + +static int db5500_kp_cols[] = { + COL_PIN_O4, COL_PIN_O5, +}; + +static bool db5500_config; +static int db5500_set_gpio_row(int gpio) +{ + int ret = -1; + + + if (!db5500_config) { + ret = gpio_request(gpio, "db5500_kpd"); + if (ret < 0) { + pr_err("db5500_set_gpio_row: gpio request failed\n"); + return ret; + } + } + + ret = gpio_direction_output(gpio, 1); + if (ret < 0) { + pr_err("db5500_set_gpio_row: gpio direction failed\n"); + gpio_free(gpio); + } + + return ret; +} + +static int db5500_kp_init(void) +{ + struct ux500_pins *pins; + int ret, i; + + pins = ux500_pins_get("db5500_kp"); + if (pins) + ux500_pins_enable(pins); + + for (i = 0; i < ARRAY_SIZE(db5500_kp_rows); i++) { + ret = db5500_set_gpio_row(db5500_kp_rows[i]); + if (ret < 0) { + pr_err("db5500_kp_init: failed init\n"); + return ret; + } + } + + if (!db5500_config) + db5500_config = true; + + return 0; +} + +static int db5500_kp_exit(void) +{ + struct ux500_pins *pins; + + pins = ux500_pins_get("db5500_kp"); + if (pins) + ux500_pins_disable(pins); + + return 0; +} + +static const unsigned int u5500_keymap[] = { + KEY(4, 0, KEY_CAMERA), /* Camera2 */ + KEY(4, 1, KEY_CAMERA_FOCUS), /* Camera1 */ + KEY(4, 2, KEY_MENU), + KEY(4, 3, KEY_BACK), + KEY(5, 2, KEY_SEND), + KEY(5, 3, KEY_HOME), +#ifndef CONFIG_INPUT_AB8500_PONKEY + /* AB5500 ONSWa is also hooked up to this key */ + KEY(8, 0, KEY_END), +#endif + KEY(8, 1, KEY_VOLUMEUP), + KEY(8, 2, KEY_VOLUMEDOWN), +}; + +static struct matrix_keymap_data u5500_keymap_data = { + .keymap = u5500_keymap, + .keymap_size = ARRAY_SIZE(u5500_keymap), +}; + +static struct db5500_keypad_platform_data u5500_keypad_board = { + .init = db5500_kp_init, + .exit = db5500_kp_exit, + .gpio_input_pins = db5500_kp_rows, + .gpio_output_pins = db5500_kp_cols, + .keymap_data = &u5500_keymap_data, + .no_autorepeat = true, + .krow = ARRAY_SIZE(db5500_kp_rows), + .kcol = ARRAY_SIZE(db5500_kp_cols), + .debounce_ms = 40, /* milliseconds */ + .switch_delay = 200, /* in jiffies */ +}; + +#ifdef CONFIG_STM_I2S +/* + * MSP + */ + +#define MSP_DMA(num, eventline) \ +static struct stedma40_chan_cfg msp##num##_dma_rx = { \ + .high_priority = true, \ + .dir = STEDMA40_PERIPH_TO_MEM, \ + .src_dev_type = eventline##_RX, \ + .dst_dev_type = STEDMA40_DEV_DST_MEMORY, \ + .src_info.psize = STEDMA40_PSIZE_LOG_4, \ + .dst_info.psize = STEDMA40_PSIZE_LOG_4, \ +}; \ + \ +static struct stedma40_chan_cfg msp##num##_dma_tx = { \ + .high_priority = true, \ + .dir = STEDMA40_MEM_TO_PERIPH, \ + .src_dev_type = STEDMA40_DEV_SRC_MEMORY, \ + .dst_dev_type = eventline##_TX, \ + .src_info.psize = STEDMA40_PSIZE_LOG_4, \ + .dst_info.psize = STEDMA40_PSIZE_LOG_4, \ +} + +MSP_DMA(0, DB5500_DMA_DEV9_MSP0); +MSP_DMA(1, DB5500_DMA_DEV10_MSP1); +MSP_DMA(2, DB5500_DMA_DEV11_MSP2); + +static struct msp_i2s_platform_data u5500_msp0_data = { + .id = MSP_0_I2S_CONTROLLER, + .msp_i2s_dma_rx = &msp0_dma_rx, + .msp_i2s_dma_tx = &msp0_dma_tx, +}; + +static struct msp_i2s_platform_data u5500_msp1_data = { + .id = MSP_1_I2S_CONTROLLER, + .msp_i2s_dma_rx = &msp1_dma_rx, + .msp_i2s_dma_tx = &msp1_dma_tx, +}; + +static struct msp_i2s_platform_data u5500_msp2_data = { + .id = MSP_2_I2S_CONTROLLER, + .msp_i2s_dma_rx = &msp2_dma_rx, + .msp_i2s_dma_tx = &msp2_dma_tx, +}; + +static struct i2s_board_info stm_i2s_board_info[] __initdata = { + { + .modalias = "i2s_device.0", + .id = 0, + .chip_select = 0, + }, + { + .modalias = "i2s_device.1", + .id = 1, + .chip_select = 1, + }, + { + .modalias = "i2s_device.2", + .id = 2, + .chip_select = 2, + }, +}; + +static void __init u5500_msp_init(void) +{ + db5500_add_msp0_i2s(&u5500_msp0_data); + db5500_add_msp1_i2s(&u5500_msp1_data); + db5500_add_msp2_i2s(&u5500_msp2_data); + + i2s_register_board_info(ARRAY_AND_SIZE(stm_i2s_board_info)); +} +#else +static void __init u5500_msp_init(void) +{ +} +#endif + +/* + * SPI + */ + +static struct pl022_ssp_controller u5500_spi3_data = { + .bus_id = 1, + .num_chipselect = 4, /* 3 possible CS lines + 1 for tests */ +}; + +static void __init u5500_spi_init(void) +{ + db5500_add_spi3(&u5500_spi3_data); +} + +static struct resource ab5500_resources[] = { + [0] = { + .start = IRQ_DB5500_PRCMU_ABB, + .end = IRQ_DB5500_PRCMU_ABB, + .flags = IORESOURCE_IRQ + } }; + +#ifdef CONFIG_INPUT_AB5500_ACCDET +static struct abx500_accdet_platform_data ab5500_accdet_pdata = { + .btn_keycode = KEY_MEDIA, + .accdet1_dbth = ACCDET1_TH_300mV | ACCDET1_DB_10ms, + .accdet2122_th = ACCDET21_TH_300mV | ACCDET22_TH_300mV, + .is_detection_inverted = false, + }; +#endif + +static struct ab5500_platform_data ab5500_plf_data = { + .irq = { + .base = IRQ_AB5500_BASE, + .count = AB5500_NR_IRQS, + }, + .pm_power_off = true, + .regulator = &u5500_ab5500_regulator_data, +#ifdef CONFIG_INPUT_AB5500_ACCDET + .dev_data[AB5500_DEVID_ACCDET] = &ab5500_accdet_pdata, + .dev_data_sz[AB5500_DEVID_ACCDET] = sizeof(ab5500_accdet_pdata), +#endif + .dev_data[AB5500_DEVID_LEDS] = &ab5500_hvleds_data, + .dev_data_sz[AB5500_DEVID_LEDS] = sizeof(ab5500_hvleds_data), + .init_settings = (struct abx500_init_settings[]){ + { + .bank = 0x3, + .reg = 0x17, + .setting = 0x0F, + }, + { + .bank = 0x3, + .reg = 0x18, + .setting = 0x10, + }, + }, + .init_settings_sz = 2, +#if defined(CONFIG_AB5500_BM) + .dev_data[AB5500_DEVID_CHARGALG] = &abx500_bm_pt_data, + .dev_data_sz[AB5500_DEVID_CHARGALG] = sizeof(abx500_bm_pt_data), + .dev_data[AB5500_DEVID_CHARGER] = &abx500_bm_pt_data, + .dev_data_sz[AB5500_DEVID_CHARGER] = sizeof(abx500_bm_pt_data), + .dev_data[AB5500_DEVID_FG] = &abx500_bm_pt_data, + .dev_data_sz[AB5500_DEVID_FG] = sizeof(abx500_bm_pt_data), + .dev_data[AB5500_DEVID_BTEMP] = &abx500_bm_pt_data, + .dev_data_sz[AB5500_DEVID_BTEMP] = sizeof(abx500_bm_pt_data), +#endif + .dev_data[AB5500_DEVID_ONSWA] = &ab5500_ponkey_data, + .dev_data_sz[AB5500_DEVID_ONSWA] = sizeof(ab5500_ponkey_data), + .dev_data[AB5500_DEVID_USB] = &abx500_usbgpio_plat_data, + .dev_data_sz[AB5500_DEVID_USB] = sizeof(abx500_usbgpio_plat_data), +}; + +static struct platform_device u5500_ab5500_device = { + .name = "ab5500-core", + .id = 0, + .dev = { + .platform_data = &ab5500_plf_data, + }, + .num_resources = 1, + .resource = ab5500_resources, +}; + +static struct platform_device u5500_mloader_device = { + .name = "db5500_mloader", + .id = -1, + .num_resources = 0, +}; + +static struct cryp_platform_data u5500_cryp1_platform_data = { + .mem_to_engine = { + .dir = STEDMA40_MEM_TO_PERIPH, + .src_dev_type = STEDMA40_DEV_SRC_MEMORY, + .dst_dev_type = DB5500_DMA_DEV48_CRYPTO1_TX, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, + .mode = STEDMA40_MODE_LOGICAL, + .src_info.psize = STEDMA40_PSIZE_LOG_4, + .dst_info.psize = STEDMA40_PSIZE_LOG_4, + }, + .engine_to_mem = { + .dir = STEDMA40_PERIPH_TO_MEM, + .src_dev_type = DB5500_DMA_DEV48_CRYPTO1_RX, + .dst_dev_type = STEDMA40_DEV_DST_MEMORY, + .src_info.data_width = STEDMA40_WORD_WIDTH, + .dst_info.data_width = STEDMA40_WORD_WIDTH, + .mode = STEDMA40_MODE_LOGICAL, + .src_info.psize = STEDMA40_PSIZE_LOG_4, + .dst_info.psize = STEDMA40_PSIZE_LOG_4, + } +}; + +static struct hash_platform_data u5500_hash1_platform_data = { + .mem_to_engine = { +.dir = STEDMA40_MEM_TO_PERIPH, +.src_dev_type = STEDMA40_DEV_SRC_MEMORY, +.dst_dev_type = DB5500_DMA_DEV50_HASH1_TX, +.src_info.data_width = STEDMA40_WORD_WIDTH, +.dst_info.data_width = STEDMA40_WORD_WIDTH, +.mode = STEDMA40_MODE_LOGICAL, +.src_info.psize = STEDMA40_PSIZE_LOG_16, +.dst_info.psize = STEDMA40_PSIZE_LOG_16, +}, +}; + +static struct platform_device *u5500_platform_devices[] __initdata = { + &u5500_ab5500_device, +#ifdef CONFIG_FB_MCDE + &u5500_mcde_device, +#endif + &ux500_hwmem_device, + &u5500_b2r2_device, + &u5500_mloader_device, +#ifdef CONFIG_U5500_MMIO + &u5500_mmio_device, +#endif + &u5500_thsens_device, +}; + +/* + * This function check whether it is Small S5500 board + * GPIO0 is HIGH for S5500 + */ +bool is_s5500_board() +{ + int err , val ; + + err = gpio_request(GPIO_BOARD_VERSION, "Board Version"); + if (err) { + pr_err("Error %d while requesting GPIO for Board Version\n", + err); + return err; + } + + err = gpio_direction_input(GPIO_BOARD_VERSION); + if (err) { + pr_err("Error %d while setting GPIO for Board Version" + "output mode\n", err); + return err; + } + + val = gpio_get_value(GPIO_BOARD_VERSION); + + gpio_free(GPIO_BOARD_VERSION); + + return (val == 1); +} + + static void __init u5500_i2c_init(void) { + db5500_add_i2c1(&u5500_i2c1_data); db5500_add_i2c2(&u5500_i2c2_data); + db5500_add_i2c3(&u5500_i2c3_data); + + i2c_register_board_info(1, ARRAY_AND_SIZE(u5500_i2c1_devices)); i2c_register_board_info(2, ARRAY_AND_SIZE(u5500_i2c2_devices)); + + if (cpu_is_u5500v1()) + i2c_register_board_info(2, ARRAY_AND_SIZE(u5500v1_i2c2_sensor_devices)); + + if (cpu_is_u5500v2()) { + /* + * In V2 display is mounted in reverse direction, + * so need to change the intial + * settings of Accelerometer and Magnetometer + */ + lsm303dlh_pdata.negative_x = 1; + lsm303dlh_pdata.negative_y = 1; + i2c_register_board_info(2, ARRAY_AND_SIZE(u5500v2_i2c2_sensor_devices)); + } } + static void __init u5500_uart_init(void) { db5500_add_uart0(NULL); db5500_add_uart1(NULL); db5500_add_uart2(NULL); + db5500_add_uart3(NULL); +} + +static void __init u5500_cryp1_hash1_init(void) +{ + db5500_add_cryp1(&u5500_cryp1_platform_data); + db5500_add_hash1(&u5500_hash1_platform_data); } static void __init u5500_init_machine(void) { + u5500_regulators_init(); u5500_init_devices(); - nmk_config_pins(u5500_pins, ARRAY_SIZE(u5500_pins)); + u5500_pins_init(); + u5500_i2c_init(); + u5500_msp_init(); + u5500_spi_init(); + u5500_sdi_init(); u5500_uart_init(); + + u5500_wlan_init(); + + db5500_add_keypad(&u5500_keypad_board); + u5500_cryp1_hash1_init(); + +#ifdef CONFIG_TOUCHSCREEN_CYTTSP_SPI + u5500_cyttsp_init(); +#endif + + platform_add_devices(u5500_platform_devices, + ARRAY_SIZE(u5500_platform_devices)); } MACHINE_START(U5500, "ST-Ericsson U5500 Platform") @@ -124,3 +668,11 @@ MACHINE_START(U5500, "ST-Ericsson U5500 Platform") .timer = &ux500_timer, .init_machine = u5500_init_machine, MACHINE_END + +MACHINE_START(B5500, "ST-Ericsson U5500 Big Board") + .atag_offset = 0x00000100, + .map_io = u5500_map_io, + .init_irq = ux500_init_irq, + .timer = &ux500_timer, + .init_machine = u5500_init_machine, +MACHINE_END diff --git a/arch/arm/mach-ux500/board-u5500.h b/arch/arm/mach-ux500/board-u5500.h new file mode 100644 index 00000000000..85762e5efb1 --- /dev/null +++ b/arch/arm/mach-ux500/board-u5500.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef __BOARD_U5500_H +#define __BOARD_U5500_H + +#define GPIO_SDMMC_CD 180 +#define GPIO_MMC_CARD_CTRL 227 +#define GPIO_MMC_CARD_VSEL 185 +#define GPIO_BOARD_VERSION 0 +#define GPIO_PRIMARY_CAM_XSHUTDOWN 1 +#define GPIO_SECONDARY_CAM_XSHUTDOWN 2 +#define GPIO_CAMERA_PMIC_EN 212 +#define CYPRESS_TOUCH_INT_PIN 179 +#define CYPRESS_TOUCH_RST_GPIO 135 +#define CYPRESS_SLAVE_SELECT_GPIO 186 + + +struct ab5500_regulator_platform_data; +extern struct ab5500_regulator_platform_data u5500_ab5500_regulator_data; + +extern void u5500_pins_init(void); +extern void __init u5500_regulators_init(void); +void u5500_cyttsp_init(void); +bool is_s5500_board(); + +#endif diff --git a/arch/arm/mach-ux500/clock-db5500.c b/arch/arm/mach-ux500/clock-db5500.c new file mode 100644 index 00000000000..163c48d9f24 --- /dev/null +++ b/arch/arm/mach-ux500/clock-db5500.c @@ -0,0 +1,715 @@ +/* + * Copyright (C) 2009 ST-Ericsson SA + * Copyright (C) 2009 STMicroelectronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/init.h> +#include <linux/list.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/workqueue.h> +#include <linux/gpio/nomadik.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include <plat/pincfg.h> + +#include <mach/hardware.h> + +#include "clock.h" +#include "pins-db5500.h" + +static DEFINE_MUTEX(sysclk_mutex); +static DEFINE_MUTEX(pll_mutex); + +/* SysClk operations. */ +static int sysclk_enable(struct clk *clk) +{ + return prcmu_request_clock(PRCMU_SYSCLK, true); +} + +static void sysclk_disable(struct clk *clk) +{ + + prcmu_request_clock(PRCMU_SYSCLK, false); + return; +} + +static struct clkops sysclk_ops = { + .enable = sysclk_enable, + .disable = sysclk_disable, +}; + +static int rtc_clk_enable(struct clk *clk) +{ + return ab5500_clock_rtc_enable(clk->cg_sel, true); +} + +static void rtc_clk_disable(struct clk *clk) +{ + int ret = ab5500_clock_rtc_enable(clk->cg_sel, false); + + if (ret) + pr_err("clock: %s failed to disable: %d\n", clk->name, ret); +} + +static struct clkops rtc_clk_ops = { + .enable = rtc_clk_enable, + .disable = rtc_clk_disable, +}; + +static pin_cfg_t clkout0_pins[] = { + GPIO161_CLKOUT_0 | PIN_OUTPUT_LOW, +}; + +static pin_cfg_t clkout1_pins[] = { + GPIO162_CLKOUT_1 | PIN_OUTPUT_LOW, +}; + +static int clkout0_enable(struct clk *clk) +{ + return nmk_config_pins(clkout0_pins, ARRAY_SIZE(clkout0_pins)); +} + +static void clkout0_disable(struct clk *clk) +{ + int r; + + r = nmk_config_pins_sleep(clkout0_pins, ARRAY_SIZE(clkout0_pins)); + if (!r) + return; + + pr_err("clock: failed to disable %s.\n", clk->name); +} + +static int clkout1_enable(struct clk *clk) +{ + return nmk_config_pins(clkout1_pins, ARRAY_SIZE(clkout0_pins)); +} + +static void clkout1_disable(struct clk *clk) +{ + int r; + + r = nmk_config_pins_sleep(clkout1_pins, ARRAY_SIZE(clkout1_pins)); + if (!r) + return; + + pr_err("clock: failed to disable %s.\n", clk->name); +} + +static struct clkops clkout0_ops = { + .enable = clkout0_enable, + .disable = clkout0_disable, +}; + +static struct clkops clkout1_ops = { + .enable = clkout1_enable, + .disable = clkout1_disable, +}; + +#define DEF_PER1_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U5500_CLKRST1_BASE, _cg_bit, &per1clk) +#define DEF_PER2_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U5500_CLKRST2_BASE, _cg_bit, &per2clk) +#define DEF_PER3_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U5500_CLKRST3_BASE, _cg_bit, &per3clk) +#define DEF_PER5_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U5500_CLKRST5_BASE, _cg_bit, &per5clk) +#define DEF_PER6_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U5500_CLKRST6_BASE, _cg_bit, &per6clk) + +#define DEF_PER1_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U5500_CLKRST1_BASE, _cg_bit, _parent, &per1clk) +#define DEF_PER2_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U5500_CLKRST2_BASE, _cg_bit, _parent, &per2clk) +#define DEF_PER3_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U5500_CLKRST3_BASE, _cg_bit, _parent, &per3clk) +#define DEF_PER5_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U5500_CLKRST5_BASE, _cg_bit, _parent, &per5clk) +#define DEF_PER6_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U5500_CLKRST6_BASE, _cg_bit, _parent, &per6clk) + +/* Clock sources. */ + +static struct clk soc0_pll = { + .name = "soc0_pll", + .ops = &prcmu_clk_ops, + .mutex = &pll_mutex, + .cg_sel = PRCMU_PLLSOC0, +}; + +static struct clk soc1_pll = { + .name = "soc1_pll", + .ops = &prcmu_clk_ops, + .mutex = &pll_mutex, + .cg_sel = PRCMU_PLLSOC1, +}; + +static struct clk ddr_pll = { + .name = "ddr_pll", + .ops = &prcmu_clk_ops, + .mutex = &pll_mutex, + .cg_sel = PRCMU_PLLDDR, +}; + +static struct clk sysclk = { + .name = "sysclk", + .ops = &sysclk_ops, + .rate = 26000000, + .mutex = &sysclk_mutex, +}; + +static struct clk rtc32k = { + .name = "rtc32k", + .rate = 32768, +}; + +static struct clk kbd32k = { + .name = "kbd32k", + .rate = 32768, +}; + +static struct clk clk_dummy = { + .name = "dummy", +}; + +static struct clk rtc_clk1 = { + .name = "rtc_clk1", + .ops = &rtc_clk_ops, + .cg_sel = 1, + .mutex = &sysclk_mutex, +}; + +static struct clk clkout0 = { + .name = "clkout0", + .ops = &clkout0_ops, + .parent = &sysclk, + .mutex = &sysclk_mutex, +}; + +static struct clk clkout1 = { + .name = "clkout1", + .ops = &clkout1_ops, + .parent = &sysclk, + .mutex = &sysclk_mutex, +}; + +static DEFINE_MUTEX(parented_prcmu_mutex); + +#define DEF_PRCMU_CLK_PARENT(_name, _cg_sel, _rate, _parent) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcmu_clk_ops, \ + .cg_sel = _cg_sel, \ + .rate = _rate, \ + .parent = _parent, \ + .mutex = &parented_prcmu_mutex, \ + } + +static DEFINE_MUTEX(prcmu_client_mutex); + +#define DEF_PRCMU_CLIENT_CLK(_name, _cg_sel, _rate) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcmu_clk_ops, \ + .cg_sel = _cg_sel, \ + .rate = _rate, \ + .mutex = &prcmu_client_mutex, \ + } + +static DEF_PRCMU_CLK(dmaclk, PRCMU_DMACLK, 200000000); +static DEF_PRCMU_CLK(b2r2clk, PRCMU_B2R2CLK, 200000000); +static DEF_PRCMU_CLK(sgaclk, PRCMU_SGACLK, 199900000); +static DEF_PRCMU_CLK(uartclk, PRCMU_UARTCLK, 36360000); +static DEF_PRCMU_CLK(msp02clk, PRCMU_MSP02CLK, 13000000); +static DEF_PRCMU_CLIENT_CLK(msp1clk, PRCMU_MSP1CLK, 26000000); +static DEF_PRCMU_CLIENT_CLK(cdclk, PRCMU_CDCLK, 26000000); +static DEF_PRCMU_CLK(i2cclk, PRCMU_I2CCLK, 24000000); +static DEF_PRCMU_CLK(irdaclk, PRCMU_IRDACLK, 48000000); +static DEF_PRCMU_CLK(irrcclk, PRCMU_IRRCCLK, 48000000); +static DEF_PRCMU_CLK(rngclk, PRCMU_RNGCLK, 26000000); +static DEF_PRCMU_CLK(pwmclk, PRCMU_PWMCLK, 26000000); +static DEF_PRCMU_CLK(sdmmcclk, PRCMU_SDMMCCLK, 50000000); +static DEF_PRCMU_CLK(spare1clk, PRCMU_SPARE1CLK, 50000000); +static DEF_PRCMU_CLK(per1clk, PRCMU_PER1CLK, 133330000); +static DEF_PRCMU_CLK(per2clk, PRCMU_PER2CLK, 133330000); +static DEF_PRCMU_CLK(per3clk, PRCMU_PER3CLK, 133330000); +static DEF_PRCMU_CLK(per5clk, PRCMU_PER5CLK, 133330000); +static DEF_PRCMU_CLK(per6clk, PRCMU_PER6CLK, 133330000); +static DEF_PRCMU_CLK(hdmiclk, PRCMU_HDMICLK, 26000000); +static DEF_PRCMU_CLK(apeatclk, PRCMU_APEATCLK, 200000000); +static DEF_PRCMU_CLK(apetraceclk, PRCMU_APETRACECLK, 266000000); +static DEF_PRCMU_CLK(mcdeclk, PRCMU_MCDECLK, 160000000); +static DEF_PRCMU_CLK(tvclk, PRCMU_TVCLK, 40000000); +static DEF_PRCMU_CLK(dsialtclk, PRCMU_DSIALTCLK, 400000000); +static DEF_PRCMU_CLK(timclk, PRCMU_TIMCLK, 3250000); +static DEF_PRCMU_CLK_PARENT(svaclk, PRCMU_SVACLK, 156000000, &soc1_pll); +static DEF_PRCMU_CLK(siaclk, PRCMU_SIACLK, 133330000); + +/* PRCC PClocks */ + +static DEF_PER1_PCLK(0, p1_pclk0); +static DEF_PER1_PCLK(1, p1_pclk1); +static DEF_PER1_PCLK(2, p1_pclk2); +static DEF_PER1_PCLK(3, p1_pclk3); +static DEF_PER1_PCLK(4, p1_pclk4); +static DEF_PER1_PCLK(5, p1_pclk5); +static DEF_PER1_PCLK(6, p1_pclk6); + +static DEF_PER2_PCLK(0, p2_pclk0); +static DEF_PER2_PCLK(1, p2_pclk1); + +static DEF_PER3_PCLK(0, p3_pclk0); +static DEF_PER3_PCLK(1, p3_pclk1); +static DEF_PER3_PCLK(2, p3_pclk2); + +static DEF_PER5_PCLK(0, p5_pclk0); +static DEF_PER5_PCLK(1, p5_pclk1); +static DEF_PER5_PCLK(2, p5_pclk2); +static DEF_PER5_PCLK(3, p5_pclk3); +static DEF_PER5_PCLK(4, p5_pclk4); +static DEF_PER5_PCLK(5, p5_pclk5); +static DEF_PER5_PCLK(6, p5_pclk6); +static DEF_PER5_PCLK(7, p5_pclk7); +static DEF_PER5_PCLK(8, p5_pclk8); +static DEF_PER5_PCLK(9, p5_pclk9); +static DEF_PER5_PCLK(10, p5_pclk10); +static DEF_PER5_PCLK(11, p5_pclk11); +static DEF_PER5_PCLK(12, p5_pclk12); +static DEF_PER5_PCLK(13, p5_pclk13); +static DEF_PER5_PCLK(14, p5_pclk14); +static DEF_PER5_PCLK(15, p5_pclk15); + +static DEF_PER6_PCLK(0, p6_pclk0); +static DEF_PER6_PCLK(1, p6_pclk1); +static DEF_PER6_PCLK(2, p6_pclk2); +static DEF_PER6_PCLK(3, p6_pclk3); +static DEF_PER6_PCLK(4, p6_pclk4); +static DEF_PER6_PCLK(5, p6_pclk5); +static DEF_PER6_PCLK(6, p6_pclk6); +static DEF_PER6_PCLK(7, p6_pclk7); + +/* MSP0 */ +static DEF_PER1_KCLK(0, p1_msp0_kclk, &msp02clk); +static DEF_PER_CLK(p1_msp0_clk, &p1_pclk0, &p1_msp0_kclk); + +/* SDI0 */ +static DEF_PER1_KCLK(1, p1_sdi0_kclk, &spare1clk); /* &sdmmcclk on v1 */ +static DEF_PER_CLK(p1_sdi0_clk, &p1_pclk1, &p1_sdi0_kclk); + +/* SDI2 */ +static DEF_PER1_KCLK(2, p1_sdi2_kclk, &sdmmcclk); +static DEF_PER_CLK(p1_sdi2_clk, &p1_pclk2, &p1_sdi2_kclk); + +/* UART0 */ +static DEF_PER1_KCLK(3, p1_uart0_kclk, &uartclk); +static DEF_PER_CLK(p1_uart0_clk, &p1_pclk3, &p1_uart0_kclk); + +/* I2C1 */ +static DEF_PER1_KCLK(4, p1_i2c1_kclk, &i2cclk); +static DEF_PER_CLK(p1_i2c1_clk, &p1_pclk4, &p1_i2c1_kclk); + +/* PWM */ +static DEF_PER3_KCLK(0, p3_pwm_kclk, &pwmclk); +static DEF_PER_CLK(p3_pwm_clk, &p3_pclk1, &p3_pwm_kclk); + +/* KEYPAD */ +static DEF_PER3_KCLK(0, p3_keypad_kclk, &kbd32k); +static DEF_PER_CLK(p3_keypad_clk, &p3_pclk0, &p3_keypad_kclk); + +/* MSP2 */ +static DEF_PER5_KCLK(0, p5_msp2_kclk, &msp02clk); +static DEF_PER_CLK(p5_msp2_clk, &p5_pclk0, &p5_msp2_kclk); + +/* UART1 */ +static DEF_PER5_KCLK(1, p5_uart1_kclk, &uartclk); +static DEF_PER_CLK(p5_uart1_clk, &p5_pclk1, &p5_uart1_kclk); + +/* UART2 */ +static DEF_PER5_KCLK(2, p5_uart2_kclk, &uartclk); +static DEF_PER_CLK(p5_uart2_clk, &p5_pclk2, &p5_uart2_kclk); + +/* UART3 */ +static DEF_PER5_KCLK(3, p5_uart3_kclk, &uartclk); +static DEF_PER_CLK(p5_uart3_clk, &p5_pclk3, &p5_uart3_kclk); + +/* SDI1 */ +static DEF_PER5_KCLK(4, p5_sdi1_kclk, &sdmmcclk); +static DEF_PER_CLK(p5_sdi1_clk, &p5_pclk4, &p5_sdi1_kclk); + +/* SDI3 */ +static DEF_PER5_KCLK(5, p5_sdi3_kclk, &sdmmcclk); +static DEF_PER_CLK(p5_sdi3_clk, &p5_pclk5, &p5_sdi3_kclk); + +/* SDI4 */ +static DEF_PER5_KCLK(6, p5_sdi4_kclk, &sdmmcclk); +static DEF_PER_CLK(p5_sdi4_clk, &p5_pclk6, &p5_sdi4_kclk); + +/* I2C2 */ +static DEF_PER5_KCLK(7, p5_i2c2_kclk, &i2cclk); +static DEF_PER_CLK(p5_i2c2_clk, &p5_pclk7, &p5_i2c2_kclk); + +/* I2C3 */ +static DEF_PER5_KCLK(8, p5_i2c3_kclk, &i2cclk); +static DEF_PER_CLK(p5_i2c3_clk, &p5_pclk8, &p5_i2c3_kclk); + +/* IRRC */ +static DEF_PER5_KCLK(9, p5_irrc_kclk, &irrcclk); +static DEF_PER_CLK(p5_irrc_clk, &p5_pclk9, &p5_irrc_kclk); + +/* IRDA */ +static DEF_PER5_KCLK(10, p5_irda_kclk, &irdaclk); +static DEF_PER_CLK(p5_irda_clk, &p5_pclk10, &p5_irda_kclk); + +/* RNG */ +static DEF_PER6_KCLK(0, p6_rng_kclk, &rngclk); +static DEF_PER_CLK(p6_rng_clk, &p6_pclk0, &p6_rng_kclk); + +/* MTU:S */ + +/* MTU0 */ +static DEF_PER_CLK(p6_mtu0_clk, &p6_pclk6, &timclk); + +/* MTU1 */ +static DEF_PER_CLK(p6_mtu1_clk, &p6_pclk7, &timclk); + +static struct clk *db5500_dbg_clks[] __initdata = { + /* Clock sources */ + &soc0_pll, + &soc1_pll, + &ddr_pll, + &sysclk, + &rtc32k, + + /* PRCMU clocks */ + &sgaclk, + &siaclk, + &svaclk, + &uartclk, + &msp02clk, + &msp1clk, + &cdclk, + &i2cclk, + &irdaclk, + &irrcclk, + &sdmmcclk, + &spare1clk, + &per1clk, + &per2clk, + &per3clk, + &per5clk, + &per6clk, + &hdmiclk, + &apeatclk, + &apetraceclk, + &mcdeclk, + &dsialtclk, + &dmaclk, + &b2r2clk, + &tvclk, + &rngclk, + &pwmclk, + + /* PRCC clocks */ + &p1_pclk0, + &p1_pclk1, + &p1_pclk2, + &p1_pclk3, + &p1_pclk4, + &p1_pclk5, + &p1_pclk6, + + &p2_pclk0, + &p2_pclk1, + + &p3_pclk0, + &p3_pclk1, + &p3_pclk2, + + &p5_pclk0, + &p5_pclk1, + &p5_pclk2, + &p5_pclk3, + &p5_pclk4, + &p5_pclk5, + &p5_pclk6, + &p5_pclk7, + &p5_pclk8, + &p5_pclk9, + &p5_pclk10, + &p5_pclk11, + &p5_pclk12, + &p5_pclk13, + &p5_pclk14, + &p5_pclk15, + + &p6_pclk0, + &p6_pclk1, + &p6_pclk2, + &p6_pclk3, + &p6_pclk4, + &p6_pclk5, + &p6_pclk6, + &p6_pclk7, + + /* Clock sources */ + &clkout0, + &clkout1, + &rtc_clk1, +}; + +static struct clk_lookup u8500_common_clock_sources[] = { + CLK_LOOKUP(soc0_pll, NULL, "soc0_pll"), + CLK_LOOKUP(soc1_pll, NULL, "soc1_pll"), + CLK_LOOKUP(ddr_pll, NULL, "ddr_pll"), + CLK_LOOKUP(sysclk, NULL, "sysclk"), + CLK_LOOKUP(rtc32k, NULL, "clk32k"), +}; + +static struct clk_lookup db5500_prcmu_clocks[] = { + CLK_LOOKUP(sgaclk, "mali", NULL), + CLK_LOOKUP(siaclk, "mmio_camera", "sia"), + CLK_LOOKUP(svaclk, "hva", NULL), + CLK_LOOKUP(uartclk, "UART", NULL), + CLK_LOOKUP(msp02clk, "MSP02", NULL), + CLK_LOOKUP(msp1clk, "MSP_I2S.1", NULL), + CLK_LOOKUP(cdclk, "cable_detect.0", NULL), + CLK_LOOKUP(i2cclk, "I2C", NULL), + CLK_LOOKUP(sdmmcclk, "sdmmc", NULL), + CLK_LOOKUP(per1clk, "PERIPH1", NULL), + CLK_LOOKUP(per2clk, "PERIPH2", NULL), + CLK_LOOKUP(per3clk, "PERIPH3", NULL), + CLK_LOOKUP(per5clk, "PERIPH5", NULL), + CLK_LOOKUP(per6clk, "PERIPH6", NULL), + CLK_LOOKUP(hdmiclk, "mcde", "hdmi"), + CLK_LOOKUP(apeatclk, "apeat", NULL), + CLK_LOOKUP(apetraceclk, "apetrace", NULL), + CLK_LOOKUP(mcdeclk, "mcde", NULL), + CLK_LOOKUP(mcdeclk, "mcde", "mcde"), + CLK_LOOKUP(dmaclk, "dma40.0", NULL), + CLK_LOOKUP(b2r2clk, "b2r2", NULL), + CLK_LOOKUP(b2r2clk, "b2r2_bus", NULL), + CLK_LOOKUP(b2r2clk, "U8500-B2R2.0", NULL), + CLK_LOOKUP(tvclk, "tv", NULL), + CLK_LOOKUP(tvclk, "mcde", "tv"), +}; + +static struct clk_lookup db5500_prcc_clocks[] = { + CLK_LOOKUP(p1_msp0_clk, "MSP_I2S.0", NULL), + CLK_LOOKUP(p1_sdi0_clk, "sdi0", NULL), + CLK_LOOKUP(p1_sdi2_clk, "sdi2", NULL), + CLK_LOOKUP(p1_uart0_clk, "uart0", NULL), + CLK_LOOKUP(p1_i2c1_clk, "nmk-i2c.1", NULL), + CLK_LOOKUP(p1_pclk5, "gpio.0", NULL), + CLK_LOOKUP(p1_pclk5, "gpio.1", NULL), + CLK_LOOKUP(p1_pclk6, "fsmc", NULL), + + CLK_LOOKUP(p2_pclk0, "musb-ux500.0", "usb"), + CLK_LOOKUP(p2_pclk1, "gpio.2", NULL), + + CLK_LOOKUP(p3_keypad_clk, "db5500-keypad", NULL), + CLK_LOOKUP(p3_pwm_clk, "pwm", NULL), + CLK_LOOKUP(p3_pclk2, "gpio.4", NULL), + + CLK_LOOKUP(p5_msp2_clk, "MSP_I2S.2", NULL), + CLK_LOOKUP(p5_uart1_clk, "uart1", NULL), + CLK_LOOKUP(p5_uart2_clk, "uart2", NULL), + CLK_LOOKUP(p5_uart3_clk, "uart3", NULL), + CLK_LOOKUP(p5_sdi1_clk, "sdi1", NULL), + CLK_LOOKUP(p5_sdi3_clk, "sdi3", NULL), + CLK_LOOKUP(p5_sdi4_clk, "sdi4", NULL), + CLK_LOOKUP(p5_i2c2_clk, "nmk-i2c.2", NULL), + CLK_LOOKUP(p5_i2c3_clk, "nmk-i2c.3", NULL), + CLK_LOOKUP(p5_irrc_clk, "irrc", NULL), + CLK_LOOKUP(p5_irda_clk, "irda", NULL), + CLK_LOOKUP(p5_pclk11, "spi0", NULL), + CLK_LOOKUP(p5_pclk12, "spi1", NULL), + CLK_LOOKUP(p5_pclk13, "spi2", NULL), + CLK_LOOKUP(p5_pclk14, "spi3", NULL), + CLK_LOOKUP(p5_pclk15, "gpio.5", NULL), + CLK_LOOKUP(p5_pclk15, "gpio.6", NULL), + CLK_LOOKUP(p5_pclk15, "gpio.7", NULL), + + CLK_LOOKUP(p6_rng_clk, "rng", NULL), + CLK_LOOKUP(p6_pclk1, "cryp0", NULL), + CLK_LOOKUP(p6_pclk2, "hash0", NULL), + CLK_LOOKUP(p6_pclk3, "pka", NULL), + CLK_LOOKUP(p6_pclk4, "hash1", NULL), + CLK_LOOKUP(p6_pclk1, "cryp1", NULL), + CLK_LOOKUP(p6_pclk5, "cfgreg", NULL), + CLK_LOOKUP(p6_mtu0_clk, "mtu0", NULL), + CLK_LOOKUP(p6_mtu1_clk, "mtu1", NULL), + + /* + * Dummy clock sets up the GPIOs. + */ + CLK_LOOKUP(clk_dummy, "gpio.3", NULL), +}; + +static struct clk_lookup db5500_clkouts[] = { + CLK_LOOKUP(clkout1, "mmio_camera", "primary-cam"), + CLK_LOOKUP(clkout1, "mmio_camera", "secondary-cam"), +}; + +static struct clk_lookup u5500_clocks[] = { + CLK_LOOKUP(rtc_clk1, "cg2900-uart.0", "lpoclk"), +}; + +static const char *db5500_boot_clk[] __initdata = { + "spi0", + "spi1", + "spi2", + "spi3", + "uart0", + "uart1", + "uart2", + "uart3", + "sdi0", + "sdi1", + "sdi2", + "sdi3", + "sdi4", +}; + +static struct clk *boot_clks[ARRAY_SIZE(db5500_boot_clk)] __initdata; + +static int __init db5500_boot_clk_disable(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(db5500_boot_clk); i++) { + clk_disable(boot_clks[i]); + clk_put(boot_clks[i]); + } + + return 0; +} +late_initcall_sync(db5500_boot_clk_disable); + +static void __init db5500_boot_clk_enable(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(db5500_boot_clk); i++) { + boot_clks[i] = clk_get_sys(db5500_boot_clk[i], NULL); + BUG_ON(IS_ERR(boot_clks[i])); + clk_enable(boot_clks[i]); + } +} + +static void configure_clkouts(void) +{ + /* div parameter does not matter for sel0 REF_CLK */ + WARN_ON(prcmu_config_clkout(DB5500_CLKOUT0, + DB5500_CLKOUT_REF_CLK_SEL0, 0)); + WARN_ON(prcmu_config_clkout(DB5500_CLKOUT1, + DB5500_CLKOUT_REF_CLK_SEL0, 0)); +} + +static struct clk *db5500_clks_tobe_disabled[] __initdata = { + &siaclk, + &sgaclk, + &sdmmcclk, + &p1_pclk0, + &p1_pclk6, + &p3_keypad_clk, + &p3_pclk1, + &p5_pclk0, + &p5_pclk11, + &p5_pclk12, + &p5_pclk13, + &p5_pclk14, + &p6_pclk4, + &p6_pclk5, + &p6_pclk7, + &p5_uart1_clk, + &p5_uart2_clk, + &p5_uart3_clk, + &p5_sdi1_clk, + &p5_sdi3_clk, + &p5_sdi4_clk, + &p5_i2c3_clk, + &p5_irrc_clk, + &p5_irda_clk, +}; + +static int __init init_clock_states(void) +{ + int i = 0; + /* + * The following clks are shared with secure world. + * Currently this leads to a limitation where we need to + * enable them at all times. + */ + clk_enable(&p6_pclk1); + clk_enable(&p6_pclk2); + clk_enable(&p6_pclk3); + clk_enable(&p6_rng_clk); + + /* + * Disable clocks that are on at boot, but should be off. + */ + for (i = 0; i < ARRAY_SIZE(db5500_clks_tobe_disabled); i++) { + if (!clk_enable(db5500_clks_tobe_disabled[i])) + clk_disable(db5500_clks_tobe_disabled[i]); + } + return 0; +} +late_initcall(init_clock_states); + +int __init db5500_clk_init(void) +{ + if (ux500_is_svp()) { + prcmu_clk_ops.enable = NULL; + prcmu_clk_ops.disable = NULL; + prcc_pclk_ops.enable = NULL; + prcc_pclk_ops.disable = NULL; + prcc_kclk_ops.enable = NULL; + prcc_kclk_ops.disable = NULL; + } + prcmu_clk_ops.get_rate = NULL; + + if (cpu_is_u5500v1()) + p1_sdi0_kclk.parent = &sdmmcclk; + + clkdev_add_table(u8500_common_clock_sources, + ARRAY_SIZE(u8500_common_clock_sources)); + + clkdev_add_table(db5500_prcmu_clocks, ARRAY_SIZE(db5500_prcmu_clocks)); + clkdev_add_table(db5500_prcc_clocks, ARRAY_SIZE(db5500_prcc_clocks)); + clkdev_add_table(db5500_clkouts, ARRAY_SIZE(db5500_clkouts)); + clkdev_add_table(u5500_clocks, ARRAY_SIZE(u5500_clocks)); + + db5500_boot_clk_enable(); + + /* + * The following clks are shared with secure world. + * Currently this leads to a limitation where we need to + * enable them at all times. + */ + clk_enable(&p6_pclk1); + clk_enable(&p6_pclk2); + clk_enable(&p6_pclk3); + clk_enable(&p6_rng_clk); + + configure_clkouts(); + + return 0; +} + +int __init db5500_clk_debug_init(void) +{ + return dbx500_clk_debug_init(db5500_dbg_clks, + ARRAY_SIZE(db5500_dbg_clks)); +} diff --git a/arch/arm/mach-ux500/clock-db8500.c b/arch/arm/mach-ux500/clock-db8500.c new file mode 100644 index 00000000000..f03d8361157 --- /dev/null +++ b/arch/arm/mach-ux500/clock-db8500.c @@ -0,0 +1,1056 @@ +/* + * Copyright (C) 2009-2011 ST-Ericsson SA + * Copyright (C) 2009 STMicroelectronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/init.h> +#include <linux/list.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/gpio/nomadik.h> +#include <linux/mfd/ab8500/sysctrl.h> +#include <linux/workqueue.h> +#include <linux/regulator/consumer.h> + +#include <plat/pincfg.h> + +#include <mach/hardware.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include "clock.h" +#include "pins-db8500.h" +#include "product.h" + +static DEFINE_MUTEX(soc1_pll_mutex); +static DEFINE_MUTEX(sysclk_mutex); +static DEFINE_MUTEX(ab_ulpclk_mutex); +static DEFINE_MUTEX(ab_intclk_mutex); +static DEFINE_MUTEX(clkout0_mutex); + +static struct delayed_work sysclk_disable_work; + +/* PLL operations. */ + +static unsigned long pll_get_rate(struct clk *clk) +{ + return prcmu_clock_rate(clk->cg_sel); +} + +static struct clkops pll_ops = { + .get_rate = pll_get_rate, +}; + +/* SysClk operations. */ + +static int request_sysclk(bool enable) +{ + static int requests; + + if ((enable && (requests++ == 0)) || (!enable && (--requests == 0))) + return prcmu_request_clock(PRCMU_SYSCLK, enable); + return 0; +} + +static int sysclk_enable(struct clk *clk) +{ + static bool swat_enable; + int r; + + if (!swat_enable) { + r = ab8500_sysctrl_set(AB8500_SWATCTRL, + AB8500_SWATCTRL_SWATENABLE); + if (r) + return r; + + swat_enable = true; + } + + r = request_sysclk(true); + if (r) + return r; + + if (clk->cg_sel) { + r = ab8500_sysctrl_set(AB8500_SYSULPCLKCTRL1, (u8)clk->cg_sel); + if (r) + (void)request_sysclk(false); + } + return r; +} + +static void sysclk_disable(struct clk *clk) +{ + int r; + + if (clk->cg_sel) { + r = ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + (u8)clk->cg_sel); + if (r) + goto disable_failed; + } + r = request_sysclk(false); + if (r) + goto disable_failed; + return; + +disable_failed: + pr_err("clock: failed to disable %s.\n", clk->name); +} + +static struct clkops sysclk_ops = { + .enable = sysclk_enable, + .disable = sysclk_disable, +}; + +/* AB8500 UlpClk operations */ + +static int ab_ulpclk_enable(struct clk *clk) +{ + int err; + + if (clk->regulator == NULL) { + struct regulator *reg; + + reg = regulator_get(NULL, "v-intcore"); + if (IS_ERR(reg)) + return PTR_ERR(reg); + clk->regulator = reg; + } + err = regulator_set_optimum_mode(clk->regulator, 1500); + if (unlikely(err < 0)) + goto regulator_enable_error; + err = regulator_enable(clk->regulator); + if (unlikely(err)) + goto regulator_enable_error; + err = ab8500_sysctrl_clear(AB8500_SYSULPCLKCONF, + AB8500_SYSULPCLKCONF_ULPCLKCONF_MASK); + if (unlikely(err)) + goto enable_error; + err = ab8500_sysctrl_set(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_ULPCLKREQ); + if (unlikely(err)) + goto enable_error; + /* Unknown/undocumented PLL locking time => wait 1 ms. */ + mdelay(1); + return 0; + +enable_error: + (void)regulator_disable(clk->regulator); +regulator_enable_error: + return err; +} + +static void ab_ulpclk_disable(struct clk *clk) +{ + int err; + + err = ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_ULPCLKREQ); + if (unlikely(regulator_disable(clk->regulator) || err)) + goto out_err; + + regulator_set_optimum_mode(clk->regulator, 0); + + return; + +out_err: + pr_err("clock: %s failed to disable %s.\n", __func__, clk->name); +} + +static struct clkops ab_ulpclk_ops = { + .enable = ab_ulpclk_enable, + .disable = ab_ulpclk_disable, +}; + +/* AB8500 intclk operations */ + +enum ab_intclk_parent { + AB_INTCLK_PARENT_SYSCLK, + AB_INTCLK_PARENT_ULPCLK, + AB_INTCLK_PARENTS_END, + NUM_AB_INTCLK_PARENTS +}; + +static int ab_intclk_enable(struct clk *clk) +{ + if (clk->parent == clk->parents[AB_INTCLK_PARENT_ULPCLK]) { + return ab8500_sysctrl_write(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_MASK, + (1 << AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_SHIFT)); + } + return 0; +} + +static void ab_intclk_disable(struct clk *clk) +{ + if (clk->parent == clk->parents[AB_INTCLK_PARENT_SYSCLK]) + return; + + if (ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_MASK)) { + pr_err("clock: %s failed to disable %s.\n", __func__, + clk->name); + } +} + +static int ab_intclk_set_parent(struct clk *clk, struct clk *parent) +{ + int err; + + if (!clk->enabled) + return 0; + + err = __clk_enable(parent, clk->mutex); + + if (unlikely(err)) + goto parent_enable_error; + + if (parent == clk->parents[AB_INTCLK_PARENT_ULPCLK]) { + err = ab8500_sysctrl_write(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_MASK, + (1 << AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_SHIFT)); + } else { + err = ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_SYSULPCLKINTSEL_MASK); + } + if (unlikely(err)) + goto config_error; + + __clk_disable(clk->parent, clk->mutex); + + return 0; + +config_error: + __clk_disable(parent, clk->mutex); +parent_enable_error: + return err; +} + +static struct clkops ab_intclk_ops = { + .enable = ab_intclk_enable, + .disable = ab_intclk_disable, + .set_parent = ab_intclk_set_parent, +}; + +/* AB8500 audio clock operations */ + +static int audioclk_enable(struct clk *clk) +{ + return ab8500_sysctrl_set(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_AUDIOCLKENA); +} + +static void audioclk_disable(struct clk *clk) +{ + if (ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + AB8500_SYSULPCLKCTRL1_AUDIOCLKENA)) { + pr_err("clock: %s failed to disable %s.\n", __func__, + clk->name); + } +} + +static struct clkops audioclk_ops = { + .enable = audioclk_enable, + .disable = audioclk_disable, +}; + +/* Primary camera clock operations */ +static int clkout0_enable(struct clk *clk) +{ + int r; + + if (clk->regulator == NULL) { + struct regulator *reg; + + reg = regulator_get(NULL, "v-ape"); + if (IS_ERR(reg)) + return PTR_ERR(reg); + clk->regulator = reg; + } + r = regulator_enable(clk->regulator); + if (r) + goto regulator_failed; + r = prcmu_config_clkout(0, PRCMU_CLKSRC_CLK38M, 4); + if (r) + goto config_failed; + r = nmk_config_pin(GPIO227_CLKOUT1, false); + if (r) + goto gpio_failed; + return r; + +gpio_failed: + (void)prcmu_config_clkout(0, PRCMU_CLKSRC_CLK38M, 0); +config_failed: + (void)regulator_disable(clk->regulator); +regulator_failed: + return r; +} + +static void clkout0_disable(struct clk *clk) +{ + int r; + + r = nmk_config_pin((GPIO227_GPIO | PIN_OUTPUT_LOW), false); + if (r) + goto disable_failed; + (void)prcmu_config_clkout(0, PRCMU_CLKSRC_CLK38M, 0); + (void)regulator_disable(clk->regulator); + return; + +disable_failed: + pr_err("clock: failed to disable %s.\n", clk->name); +} + +/* Touch screen/secondary camera clock operations. */ +static int clkout1_enable(struct clk *clk) +{ + int r; + + if (clk->regulator == NULL) { + struct regulator *reg; + + reg = regulator_get(NULL, "v-ape"); + if (IS_ERR(reg)) + return PTR_ERR(reg); + clk->regulator = reg; + } + r = regulator_enable(clk->regulator); + if (r) + goto regulator_failed; + r = prcmu_config_clkout(1, PRCMU_CLKSRC_SYSCLK, 4); + if (r) + goto config_failed; + r = nmk_config_pin(GPIO228_CLKOUT2, false); + if (r) + goto gpio_failed; + return r; + +gpio_failed: + (void)prcmu_config_clkout(1, PRCMU_CLKSRC_SYSCLK, 0); +config_failed: + (void)regulator_disable(clk->regulator); +regulator_failed: + return r; +} + +static void clkout1_disable(struct clk *clk) +{ + int r; + + r = nmk_config_pin((GPIO228_GPIO | PIN_OUTPUT_LOW), false); + if (r) + goto disable_failed; + (void)prcmu_config_clkout(1, PRCMU_CLKSRC_SYSCLK, 0); + (void)regulator_disable(clk->regulator); + return; + +disable_failed: + pr_err("clock: failed to disable %s.\n", clk->name); +} + +static struct clkops clkout0_ops = { + .enable = clkout0_enable, + .disable = clkout0_disable, +}; + +static struct clkops clkout1_ops = { + .enable = clkout1_enable, + .disable = clkout1_disable, +}; + +#define DEF_PER1_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST1_BASE, _cg_bit, &per1clk) +#define DEF_PER2_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST2_BASE, _cg_bit, &per2clk) +#define DEF_PER3_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST3_BASE, _cg_bit, &per3clk) +#define DEF_PER5_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST5_BASE, _cg_bit, &per5clk) +#define DEF_PER6_PCLK(_cg_bit, _name) \ + DEF_PRCC_PCLK(_name, U8500_CLKRST6_BASE, _cg_bit, &per6clk) + +#define DEF_PER1_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST1_BASE, _cg_bit, _parent, &per1clk) +#define DEF_PER2_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST2_BASE, _cg_bit, _parent, &per2clk) +#define DEF_PER3_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST3_BASE, _cg_bit, _parent, &per3clk) +#define DEF_PER5_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST5_BASE, _cg_bit, _parent, &per5clk) +#define DEF_PER6_KCLK(_cg_bit, _name, _parent) \ + DEF_PRCC_KCLK(_name, U8500_CLKRST6_BASE, _cg_bit, _parent, &per6clk) + +/* Clock sources. */ + +static struct clk soc0_pll = { + .name = "soc0_pll", + .ops = &pll_ops, + .cg_sel = PRCMU_PLLSOC0, +}; + +static struct clk soc1_pll = { + .name = "soc1_pll", + .ops = &prcmu_clk_ops, + .cg_sel = PRCMU_PLLSOC1, + .mutex = &soc1_pll_mutex, +}; + +static struct clk ddr_pll = { + .name = "ddr_pll", + .ops = &pll_ops, + .cg_sel = PRCMU_PLLDDR, +}; + +static struct clk ulp38m4 = { + .name = "ulp38m4", + .rate = 38400000, +}; + +static struct clk sysclk = { + .name = "sysclk", + .ops = &sysclk_ops, + .rate = 38400000, + .mutex = &sysclk_mutex, +}; + +static struct clk sysclk2 = { + .name = "sysclk2", + .ops = &sysclk_ops, + .cg_sel = AB8500_SYSULPCLKCTRL1_SYSCLKBUF2REQ, + .rate = 38400000, + .mutex = &sysclk_mutex, +}; + +static struct clk sysclk3 = { + .name = "sysclk3", + .ops = &sysclk_ops, + .cg_sel = AB8500_SYSULPCLKCTRL1_SYSCLKBUF3REQ, + .rate = 38400000, + .mutex = &sysclk_mutex, +}; + +static struct clk sysclk4 = { + .name = "sysclk4", + .ops = &sysclk_ops, + .cg_sel = AB8500_SYSULPCLKCTRL1_SYSCLKBUF4REQ, + .rate = 38400000, + .mutex = &sysclk_mutex, +}; + +static struct clk rtc32k = { + .name = "rtc32k", + .rate = 32768, +}; + +static struct clk clkout0 = { + .name = "clkout0", + .ops = &clkout0_ops, + .parent = &ulp38m4, + .rate = 9600000, + .mutex = &clkout0_mutex, +}; + +static struct clk clkout1 = { + .name = "clkout1", + .ops = &clkout1_ops, + .parent = &sysclk, + .rate = 9600000, + .mutex = &sysclk_mutex, +}; + +static struct clk ab_ulpclk = { + .name = "ab_ulpclk", + .ops = &ab_ulpclk_ops, + .rate = 38400000, + .mutex = &ab_ulpclk_mutex, +}; + +static struct clk *ab_intclk_parents[NUM_AB_INTCLK_PARENTS] = { + [AB_INTCLK_PARENT_SYSCLK] = &sysclk, + [AB_INTCLK_PARENT_ULPCLK] = &ab_ulpclk, + [AB_INTCLK_PARENTS_END] = NULL, +}; + +static struct clk ab_intclk = { + .name = "ab_intclk", + .ops = &ab_intclk_ops, + .mutex = &ab_intclk_mutex, + .parent = &sysclk, + .parents = ab_intclk_parents, +}; + +static struct clk audioclk = { + .name = "audioclk", + .ops = &audioclk_ops, + .mutex = &ab_intclk_mutex, + .parent = &ab_intclk, +}; + +static DEF_PRCMU_CLK(sgaclk, PRCMU_SGACLK, 320000000); +static DEF_PRCMU_CLK(uartclk, PRCMU_UARTCLK, 38400000); +static DEF_PRCMU_CLK(msp02clk, PRCMU_MSP02CLK, 19200000); +static DEF_PRCMU_CLK(msp1clk, PRCMU_MSP1CLK, 19200000); +static DEF_PRCMU_CLK(i2cclk, PRCMU_I2CCLK, 24000000); +static DEF_PRCMU_CLK(slimclk, PRCMU_SLIMCLK, 19200000); +static DEF_PRCMU_CLK(per1clk, PRCMU_PER1CLK, 133330000); +static DEF_PRCMU_CLK(per2clk, PRCMU_PER2CLK, 133330000); +static DEF_PRCMU_CLK(per3clk, PRCMU_PER3CLK, 133330000); +static DEF_PRCMU_CLK(per5clk, PRCMU_PER5CLK, 133330000); +static DEF_PRCMU_CLK(per6clk, PRCMU_PER6CLK, 133330000); +static DEF_PRCMU_CLK(per7clk, PRCMU_PER7CLK, 100000000); +static DEF_PRCMU_SCALABLE_CLK(lcdclk, PRCMU_LCDCLK); +static DEF_PRCMU_OPP100_CLK(bmlclk, PRCMU_BMLCLK, 200000000); +static DEF_PRCMU_SCALABLE_CLK(hsitxclk, PRCMU_HSITXCLK); +static DEF_PRCMU_SCALABLE_CLK(hsirxclk, PRCMU_HSIRXCLK); +static DEF_PRCMU_SCALABLE_CLK(hdmiclk, PRCMU_HDMICLK); +static DEF_PRCMU_CLK(apeatclk, PRCMU_APEATCLK, 160000000); +static DEF_PRCMU_CLK(apetraceclk, PRCMU_APETRACECLK, 160000000); +static DEF_PRCMU_CLK(mcdeclk, PRCMU_MCDECLK, 160000000); +static DEF_PRCMU_OPP100_CLK(ipi2cclk, PRCMU_IPI2CCLK, 24000000); +static DEF_PRCMU_CLK(dsialtclk, PRCMU_DSIALTCLK, 384000000); +static DEF_PRCMU_CLK(dmaclk, PRCMU_DMACLK, 200000000); +static DEF_PRCMU_CLK(b2r2clk, PRCMU_B2R2CLK, 200000000); +static DEF_PRCMU_SCALABLE_CLK(tvclk, PRCMU_TVCLK); +/* TODO: For SSPCLK, the spec says 24MHz, while the old driver says 48MHz. */ +static DEF_PRCMU_CLK(sspclk, PRCMU_SSPCLK, 24000000); +static DEF_PRCMU_CLK(rngclk, PRCMU_RNGCLK, 19200000); +static DEF_PRCMU_CLK(uiccclk, PRCMU_UICCCLK, 48000000); +static DEF_PRCMU_CLK(timclk, PRCMU_TIMCLK, 2400000); +static DEF_PRCMU_CLK(sdmmcclk, PRCMU_SDMMCCLK, 50000000); + +/* PRCC PClocks */ + +static DEF_PER1_PCLK(0, p1_pclk0); +static DEF_PER1_PCLK(1, p1_pclk1); +static DEF_PER1_PCLK(2, p1_pclk2); +static DEF_PER1_PCLK(3, p1_pclk3); +static DEF_PER1_PCLK(4, p1_pclk4); +static DEF_PER1_PCLK(5, p1_pclk5); +static DEF_PER1_PCLK(6, p1_pclk6); +static DEF_PER1_PCLK(7, p1_pclk7); +static DEF_PER1_PCLK(8, p1_pclk8); +static DEF_PER1_PCLK(9, p1_pclk9); +static DEF_PER1_PCLK(10, p1_pclk10); +static DEF_PER1_PCLK(11, p1_pclk11); + +static DEF_PER2_PCLK(0, p2_pclk0); +static DEF_PER2_PCLK(1, p2_pclk1); +static DEF_PER2_PCLK(2, p2_pclk2); +static DEF_PER2_PCLK(3, p2_pclk3); +static DEF_PER2_PCLK(4, p2_pclk4); +static DEF_PER2_PCLK(5, p2_pclk5); +static DEF_PER2_PCLK(6, p2_pclk6); +static DEF_PER2_PCLK(7, p2_pclk7); +static DEF_PER2_PCLK(8, p2_pclk8); +static DEF_PER2_PCLK(9, p2_pclk9); +static DEF_PER2_PCLK(10, p2_pclk10); +static DEF_PER2_PCLK(11, p2_pclk11); + +static DEF_PER3_PCLK(0, p3_pclk0); +static DEF_PER3_PCLK(1, p3_pclk1); +static DEF_PER3_PCLK(2, p3_pclk2); +static DEF_PER3_PCLK(3, p3_pclk3); +static DEF_PER3_PCLK(4, p3_pclk4); +static DEF_PER3_PCLK(5, p3_pclk5); +static DEF_PER3_PCLK(6, p3_pclk6); +static DEF_PER3_PCLK(7, p3_pclk7); +static DEF_PER3_PCLK(8, p3_pclk8); + +static DEF_PER5_PCLK(0, p5_pclk0); +static DEF_PER5_PCLK(1, p5_pclk1); + +static DEF_PER6_PCLK(0, p6_pclk0); +static DEF_PER6_PCLK(1, p6_pclk1); +static DEF_PER6_PCLK(2, p6_pclk2); +static DEF_PER6_PCLK(3, p6_pclk3); +static DEF_PER6_PCLK(4, p6_pclk4); +static DEF_PER6_PCLK(5, p6_pclk5); +static DEF_PER6_PCLK(6, p6_pclk6); +static DEF_PER6_PCLK(7, p6_pclk7); + +/* UART0 */ +static DEF_PER1_KCLK(0, p1_uart0_kclk, &uartclk); +static DEF_PER_CLK(p1_uart0_clk, &p1_pclk0, &p1_uart0_kclk); + +/* UART1 */ +static DEF_PER1_KCLK(1, p1_uart1_kclk, &uartclk); +static DEF_PER_CLK(p1_uart1_clk, &p1_pclk1, &p1_uart1_kclk); + +/* I2C1 */ +static DEF_PER1_KCLK(2, p1_i2c1_kclk, &i2cclk); +static DEF_PER_CLK(p1_i2c1_clk, &p1_pclk2, &p1_i2c1_kclk); + +/* MSP0 */ +static DEF_PER1_KCLK(3, p1_msp0_kclk, &msp02clk); +static DEF_PER_CLK(p1_msp0_clk, &p1_pclk3, &p1_msp0_kclk); + +/* MSP1 */ +static DEF_PER1_KCLK(4, p1_msp1_kclk, &msp1clk); +static DEF_PER_CLK(p1_msp1_clk, &p1_pclk4, &p1_msp1_kclk); + +/* SDI0 */ +static DEF_PER1_KCLK(5, p1_sdi0_kclk, &sdmmcclk); +static DEF_PER_CLK(p1_sdi0_clk, &p1_pclk5, &p1_sdi0_kclk); + +/* I2C2 */ +static DEF_PER1_KCLK(6, p1_i2c2_kclk, &i2cclk); +static DEF_PER_CLK(p1_i2c2_clk, &p1_pclk6, &p1_i2c2_kclk); + +/* SLIMBUS0 */ +static DEF_PER1_KCLK(3, p1_slimbus0_kclk, &slimclk); +static DEF_PER_CLK(p1_slimbus0_clk, &p1_pclk8, &p1_slimbus0_kclk); + +/* I2C4 */ +static DEF_PER1_KCLK(9, p1_i2c4_kclk, &i2cclk); +static DEF_PER_CLK(p1_i2c4_clk, &p1_pclk10, &p1_i2c4_kclk); + +/* MSP3 */ +static DEF_PER1_KCLK(10, p1_msp3_kclk, &msp1clk); +static DEF_PER_CLK(p1_msp3_clk, &p1_pclk11, &p1_msp3_kclk); + +/* I2C3 */ +static DEF_PER2_KCLK(0, p2_i2c3_kclk, &i2cclk); +static DEF_PER_CLK(p2_i2c3_clk, &p2_pclk0, &p2_i2c3_kclk); + +/* SDI4 */ +static DEF_PER2_KCLK(2, p2_sdi4_kclk, &sdmmcclk); +static DEF_PER_CLK(p2_sdi4_clk, &p2_pclk4, &p2_sdi4_kclk); + +/* MSP2 */ +static DEF_PER2_KCLK(3, p2_msp2_kclk, &msp02clk); +static DEF_PER_CLK(p2_msp2_clk, &p2_pclk5, &p2_msp2_kclk); + +/* SDI1 */ +static DEF_PER2_KCLK(4, p2_sdi1_kclk, &sdmmcclk); +static DEF_PER_CLK(p2_sdi1_clk, &p2_pclk6, &p2_sdi1_kclk); + +/* SDI3 */ +static DEF_PER2_KCLK(5, p2_sdi3_kclk, &sdmmcclk); +static DEF_PER_CLK(p2_sdi3_clk, &p2_pclk7, &p2_sdi3_kclk); + +/* HSIR */ +static struct clk p2_ssirx_kclk = { + .name = "p2_ssirx_kclk", + .ops = &prcc_kclk_rec_ops, + .io_base = U8500_CLKRST2_BASE, + .cg_sel = BIT(6), + .parent = &hsirxclk, + .clock = &per2clk, +}; + +/* HSIT */ +static struct clk p2_ssitx_kclk = { + .name = "p2_ssitx_kclk", + .ops = &prcc_kclk_rec_ops, + .io_base = U8500_CLKRST2_BASE, + .cg_sel = BIT(7), + .parent = &hsitxclk, + .clock = &per2clk, +}; + +/* SSP0 */ +static DEF_PER3_KCLK(1, p3_ssp0_kclk, &sspclk); +static DEF_PER_CLK(p3_ssp0_clk, &p3_pclk1, &p3_ssp0_kclk); + +/* SSP1 */ +static DEF_PER3_KCLK(2, p3_ssp1_kclk, &sspclk); +static DEF_PER_CLK(p3_ssp1_clk, &p3_pclk2, &p3_ssp1_kclk); + +/* I2C0 */ +static DEF_PER3_KCLK(3, p3_i2c0_kclk, &i2cclk); +static DEF_PER_CLK(p3_i2c0_clk, &p3_pclk3, &p3_i2c0_kclk); + +/* SDI2 */ +static DEF_PER3_KCLK(4, p3_sdi2_kclk, &sdmmcclk); +static DEF_PER_CLK(p3_sdi2_clk, &p3_pclk4, &p3_sdi2_kclk); + +/* SKE */ +static DEF_PER3_KCLK(5, p3_ske_kclk, &rtc32k); +static DEF_PER_CLK(p3_ske_clk, &p3_pclk5, &p3_ske_kclk); + +/* UART2 */ +static DEF_PER3_KCLK(6, p3_uart2_kclk, &uartclk); +static DEF_PER_CLK(p3_uart2_clk, &p3_pclk6, &p3_uart2_kclk); + +/* SDI5 */ +static DEF_PER3_KCLK(7, p3_sdi5_kclk, &sdmmcclk); +static DEF_PER_CLK(p3_sdi5_clk, &p3_pclk7, &p3_sdi5_kclk); + +/* RNG */ +static DEF_PER6_KCLK(0, p6_rng_kclk, &rngclk); +static DEF_PER_CLK(p6_rng_clk, &p6_pclk0, &p6_rng_kclk); + +/* MTU:S */ + +/* MTU0 */ +static DEF_PER_CLK(p6_mtu0_clk, &p6_pclk6, &timclk); + +/* MTU1 */ +static DEF_PER_CLK(p6_mtu1_clk, &p6_pclk7, &timclk); + +/* + * TODO: Ensure names match with devices and then remove unnecessary entries + * when all drivers use the clk API. + */ + +static struct clk_lookup u8500_clocks[] = { + CLK_LOOKUP(soc0_pll, NULL, "soc0_pll"), + CLK_LOOKUP(soc1_pll, NULL, "soc1_pll"), + CLK_LOOKUP(ddr_pll, NULL, "ddr_pll"), + CLK_LOOKUP(ulp38m4, NULL, "ulp38m4"), + CLK_LOOKUP(sysclk, NULL, "sysclk"), + CLK_LOOKUP(rtc32k, NULL, "clk32k"), + CLK_LOOKUP(sysclk, "ab8500-usb.0", "sysclk"), + CLK_LOOKUP(sysclk, "ab8500-codec.0", "sysclk"), + CLK_LOOKUP(ab_ulpclk, "ab8500-codec.0", "ulpclk"), + CLK_LOOKUP(ab_intclk, "ab8500-codec.0", "intclk"), + CLK_LOOKUP(audioclk, "ab8500-codec.0", "audioclk"), + CLK_LOOKUP(ab_intclk, "ab8500-pwm.1", NULL), + CLK_LOOKUP(ab_intclk, "ab8500-pwm.2", NULL), + CLK_LOOKUP(ab_intclk, "ab8500-pwm.3", NULL), + + CLK_LOOKUP(clkout0, "pri-cam", NULL), + CLK_LOOKUP(clkout1, "3-005c", NULL), + CLK_LOOKUP(clkout1, "3-005d", NULL), + CLK_LOOKUP(clkout1, "sec-cam", NULL), + + /* prcmu */ + CLK_LOOKUP(sgaclk, "mali", NULL), + CLK_LOOKUP(uartclk, "UART", NULL), + CLK_LOOKUP(msp02clk, "MSP02", NULL), + CLK_LOOKUP(i2cclk, "I2C", NULL), + CLK_LOOKUP(sdmmcclk, "sdmmc", NULL), + CLK_LOOKUP(slimclk, "slim", NULL), + CLK_LOOKUP(per1clk, "PERIPH1", NULL), + CLK_LOOKUP(per2clk, "PERIPH2", NULL), + CLK_LOOKUP(per3clk, "PERIPH3", NULL), + CLK_LOOKUP(per5clk, "PERIPH5", NULL), + CLK_LOOKUP(per6clk, "PERIPH6", NULL), + CLK_LOOKUP(per7clk, "PERIPH7", NULL), + CLK_LOOKUP(lcdclk, "lcd", NULL), + CLK_LOOKUP(bmlclk, "bml", NULL), + CLK_LOOKUP(p2_ssitx_kclk, "ste_hsi.0", "hsit_hsitxclk"), + CLK_LOOKUP(p2_ssirx_kclk, "ste_hsi.0", "hsir_hsirxclk"), + CLK_LOOKUP(lcdclk, "mcde", "lcd"), + CLK_LOOKUP(hdmiclk, "hdmi", NULL), + CLK_LOOKUP(hdmiclk, "mcde", "hdmi"), + CLK_LOOKUP(apeatclk, "apeat", NULL), + CLK_LOOKUP(apetraceclk, "apetrace", NULL), + CLK_LOOKUP(mcdeclk, "mcde", NULL), + CLK_LOOKUP(mcdeclk, "mcde", "mcde"), + CLK_LOOKUP(ipi2cclk, "ipi2", NULL), + CLK_LOOKUP(dmaclk, "dma40.0", NULL), + CLK_LOOKUP(b2r2clk, "b2r2", NULL), + CLK_LOOKUP(b2r2clk, "b2r2_bus", NULL), + CLK_LOOKUP(b2r2clk, "U8500-B2R2.0", NULL), + CLK_LOOKUP(tvclk, "tv", NULL), + CLK_LOOKUP(tvclk, "mcde", "tv"), + CLK_LOOKUP(msp1clk, "MSP1", NULL), + CLK_LOOKUP(dsialtclk, "dsialt", NULL), + CLK_LOOKUP(sspclk, "SSP", NULL), + CLK_LOOKUP(rngclk, "rngclk", NULL), + CLK_LOOKUP(uiccclk, "uicc", NULL), + + /* PERIPH 1 */ + CLK_LOOKUP(p1_msp3_clk, "msp3", NULL), + CLK_LOOKUP(p1_msp3_clk, "MSP_I2S.3", NULL), + CLK_LOOKUP(p1_msp3_kclk, "ab8500-codec.0", "msp3-kernel"), + CLK_LOOKUP(p1_pclk11, "ab8500-codec.0", "msp3-bus"), + CLK_LOOKUP(p1_uart0_clk, "uart0", NULL), + CLK_LOOKUP(p1_uart1_clk, "uart1", NULL), + CLK_LOOKUP(p1_i2c1_clk, "nmk-i2c.1", NULL), + CLK_LOOKUP(p1_msp0_clk, "msp0", NULL), + CLK_LOOKUP(p1_msp0_clk, "MSP_I2S.0", NULL), + CLK_LOOKUP(p1_sdi0_clk, "sdi0", NULL), + CLK_LOOKUP(p1_i2c2_clk, "nmk-i2c.2", NULL), + CLK_LOOKUP(p1_slimbus0_clk, "slimbus0", NULL), + CLK_LOOKUP(p1_pclk9, "gpio.0", NULL), + CLK_LOOKUP(p1_pclk9, "gpio.1", NULL), + CLK_LOOKUP(p1_pclk9, "gpioblock0", NULL), + CLK_LOOKUP(p1_msp1_clk, "msp1", NULL), + CLK_LOOKUP(p1_msp1_clk, "MSP_I2S.1", NULL), + CLK_LOOKUP(p1_msp1_kclk, "ab8500-codec.0", "msp1-kernel"), + CLK_LOOKUP(p1_pclk4, "ab8500-codec.0", "msp1-bus"), + CLK_LOOKUP(p1_pclk7, "spi3", NULL), + CLK_LOOKUP(p1_i2c4_clk, "nmk-i2c.4", NULL), + + /* PERIPH 2 */ + CLK_LOOKUP(p2_i2c3_clk, "nmk-i2c.3", NULL), + CLK_LOOKUP(p2_pclk1, "spi2", NULL), + CLK_LOOKUP(p2_pclk2, "spi1", NULL), + CLK_LOOKUP(p2_pclk3, "pwl", NULL), + CLK_LOOKUP(p2_sdi4_clk, "sdi4", NULL), + CLK_LOOKUP(p2_msp2_clk, "msp2", NULL), + CLK_LOOKUP(p2_msp2_clk, "MSP_I2S.2", NULL), + CLK_LOOKUP(p2_sdi1_clk, "sdi1", NULL), + CLK_LOOKUP(p2_sdi3_clk, "sdi3", NULL), + CLK_LOOKUP(p2_pclk8, "spi0", NULL), + CLK_LOOKUP(p2_pclk9, "ste_hsi.0", "hsir_hclk"), + CLK_LOOKUP(p2_pclk10, "ste_hsi.0", "hsit_hclk"), + CLK_LOOKUP(p2_pclk11, "gpio.6", NULL), + CLK_LOOKUP(p2_pclk11, "gpio.7", NULL), + CLK_LOOKUP(p2_pclk11, "gpioblock1", NULL), + + /* PERIPH 3 */ + CLK_LOOKUP(p3_pclk0, "fsmc", NULL), + CLK_LOOKUP(p3_i2c0_clk, "nmk-i2c.0", NULL), + CLK_LOOKUP(p3_sdi2_clk, "sdi2", NULL), + CLK_LOOKUP(p3_ske_clk, "ske", NULL), + CLK_LOOKUP(p3_ske_clk, "nmk-ske-keypad", NULL), + CLK_LOOKUP(p3_uart2_clk, "uart2", NULL), + CLK_LOOKUP(p3_sdi5_clk, "sdi5", NULL), + CLK_LOOKUP(p3_pclk8, "gpio.2", NULL), + CLK_LOOKUP(p3_pclk8, "gpio.3", NULL), + CLK_LOOKUP(p3_pclk8, "gpio.4", NULL), + CLK_LOOKUP(p3_pclk8, "gpio.5", NULL), + CLK_LOOKUP(p3_pclk8, "gpioblock2", NULL), + CLK_LOOKUP(p3_ssp0_clk, "ssp0", NULL), + CLK_LOOKUP(p3_ssp1_clk, "ssp1", NULL), + + /* PERIPH 5 */ + CLK_LOOKUP(p5_pclk1, "gpio.8", NULL), + CLK_LOOKUP(p5_pclk1, "gpioblock3", NULL), + CLK_LOOKUP(p5_pclk0, "musb-ux500.0", "usb"), + + /* PERIPH 6 */ + CLK_LOOKUP(p6_pclk1, "cryp0", NULL), + CLK_LOOKUP(p6_pclk2, "hash0", NULL), + CLK_LOOKUP(p6_pclk3, "pka", NULL), + CLK_LOOKUP(p6_pclk5, "cfgreg", NULL), + CLK_LOOKUP(p6_mtu0_clk, "mtu0", NULL), + CLK_LOOKUP(p6_mtu1_clk, "mtu1", NULL), + CLK_LOOKUP(p6_pclk4, "hash1", NULL), + CLK_LOOKUP(p6_pclk1, "cryp1", NULL), + CLK_LOOKUP(p6_rng_clk, "rng", NULL), + +}; + +static struct clk_lookup u8500_v2_sysclks[] = { + CLK_LOOKUP(sysclk2, NULL, "sysclk2"), + CLK_LOOKUP(sysclk3, NULL, "sysclk3"), + CLK_LOOKUP(sysclk4, NULL, "sysclk4"), +}; + +static void sysclk_init_disable(struct work_struct *not_used) +{ + int i; + + mutex_lock(&sysclk_mutex); + + /* Enable SWAT */ + if (ab8500_sysctrl_set(AB8500_SWATCTRL, AB8500_SWATCTRL_SWATENABLE)) + goto err_swat; + + for (i = 0; i < ARRAY_SIZE(u8500_v2_sysclks); i++) { + struct clk *clk = u8500_v2_sysclks[i].clk; + + /* Disable sysclks */ + if (!clk->enabled && clk->cg_sel) { + if (ab8500_sysctrl_clear(AB8500_SYSULPCLKCTRL1, + (u8)clk->cg_sel)) + goto err_sysclk; + } + } + goto unlock_and_exit; + +err_sysclk: + pr_err("clock: Disable %s failed", u8500_v2_sysclks[i].clk->name); + ab8500_sysctrl_clear(AB8500_SWATCTRL, AB8500_SWATCTRL_SWATENABLE); + goto unlock_and_exit; + +err_swat: + pr_err("clock: Enable SWAT failed"); + +unlock_and_exit: + mutex_unlock(&sysclk_mutex); +} + +static struct clk *db8500_dbg_clks[] __initdata = { + /* Clock sources */ + &soc0_pll, + &soc1_pll, + &ddr_pll, + &ulp38m4, + &sysclk, + &rtc32k, + /* PRCMU clocks */ + &sgaclk, + &uartclk, + &msp02clk, + &msp1clk, + &i2cclk, + &sdmmcclk, + &slimclk, + &per1clk, + &per2clk, + &per3clk, + &per5clk, + &per6clk, + &per7clk, + &lcdclk, + &bmlclk, + &hsitxclk, + &hsirxclk, + &hdmiclk, + &apeatclk, + &apetraceclk, + &mcdeclk, + &ipi2cclk, + &dsialtclk, + &dmaclk, + &b2r2clk, + &tvclk, + &sspclk, + &rngclk, + &uiccclk, + &sysclk2, + &clkout0, + &clkout1, + &p1_pclk0, + &p1_pclk1, + &p1_pclk2, + &p1_pclk3, + &p1_pclk4, + &p1_pclk5, + &p1_pclk6, + &p1_pclk7, + &p1_pclk8, + &p1_pclk9, + &p1_pclk10, + &p1_pclk11, + &p2_pclk0, + &p2_pclk1, + &p2_pclk2, + &p2_pclk3, + &p2_pclk4, + &p2_pclk5, + &p2_pclk6, + &p2_pclk7, + &p2_pclk8, + &p2_pclk9, + &p2_pclk10, + &p2_pclk11, + &p3_pclk0, + &p3_pclk1, + &p3_pclk2, + &p3_pclk3, + &p3_pclk4, + &p3_pclk5, + &p3_pclk6, + &p3_pclk7, + &p3_pclk8, + &p5_pclk0, + &p5_pclk1, + &p6_pclk0, + &p6_pclk1, + &p6_pclk2, + &p6_pclk3, + &p6_pclk4, + &p6_pclk5, + &p6_pclk6, + &p6_pclk7, +}; + +/* List of clocks which might be enabled from the bootloader */ +static struct clk *loader_enabled_clk[] __initdata = { + &p1_uart0_clk, /* uart0 */ + &p1_uart1_clk, /* uart1 */ + &p3_uart2_clk, /* uart2 */ + &p1_pclk9, /* gpioblock0 */ + &p2_pclk11, /* gpioblock1 */ + &p3_pclk8, /* gpioblock2 */ + &p5_pclk1, /* gpioblock3 */ + &p6_mtu0_clk, /* mtu0 */ + &p6_mtu1_clk, /* mtu1 */ + &p3_ssp0_clk, /* ssp0 */ + &p3_ssp1_clk, /* ssp1 */ + &p2_pclk8, /* spi0 */ + &p2_pclk2, /* spi1 */ + &p2_pclk1, /* spi2 */ + &p1_pclk7, /* spi3 */ + &p1_msp0_clk, /* msp0 */ + &p2_msp2_clk, /* msp2 */ + &p3_i2c0_clk, /* nmk-i2c.0 */ + &p1_i2c1_clk, /* nmk-i2c.1 */ + &p1_i2c2_clk, /* nmk-i2c.2 */ + &p2_i2c3_clk, /* nmk-i2c.3 */ + &p1_i2c4_clk, /* nmk-i2c.4 */ + &bmlclk, /* bml */ + &dsialtclk, /* dsialt */ + &hsirxclk, /* hsirx */ + &hsitxclk, /* hsitx */ + &ipi2cclk, /* ipi2 */ + &lcdclk, /* mcde */ + &per7clk, /* PERIPH7 */ + &b2r2clk, /* b2r2_bus */ +}; + +static int __init init_clock_states(void) +{ + unsigned int i = 0; + + /* + * Disable peripheral clocks enabled by bootloadr/defualt + * but without drivers + */ + for (i = 0; i < ARRAY_SIZE(loader_enabled_clk); i++) + if (!clk_enable(loader_enabled_clk[i])) + clk_disable(loader_enabled_clk[i]); + + /* + * The following clks are shared with secure world. + * Currently this leads to a limitation where we need to + * enable them at all times. + */ + clk_enable(&p6_pclk1); + clk_enable(&p6_pclk2); + clk_enable(&p6_pclk3); + clk_enable(&p6_rng_clk); + + /* + * APEATCLK and APETRACECLK are enabled at boot and needed + * in order to debug with Lauterbach + */ + if (!clk_enable(&apeatclk)) { + if (!ux500_jtag_enabled()) + clk_disable(&apeatclk); + } + if (!clk_enable(&apetraceclk)) { + if (!ux500_jtag_enabled()) + clk_disable(&apetraceclk); + } + + INIT_DELAYED_WORK(&sysclk_disable_work, sysclk_init_disable); + schedule_delayed_work(&sysclk_disable_work, 10 * HZ); + + return 0; +} +late_initcall(init_clock_states); + +int __init db8500_clk_init(void) +{ + + clkdev_add_table(u8500_v2_sysclks, + ARRAY_SIZE(u8500_v2_sysclks)); + clkdev_add_table(u8500_clocks, + ARRAY_SIZE(u8500_clocks)); + + return 0; +} + +int __init db8500_clk_debug_init(void) +{ + return dbx500_clk_debug_init(db8500_dbg_clks, + ARRAY_SIZE(db8500_dbg_clks)); +} diff --git a/arch/arm/mach-ux500/clock-debug.c b/arch/arm/mach-ux500/clock-debug.c new file mode 100644 index 00000000000..1ebc69fe061 --- /dev/null +++ b/arch/arm/mach-ux500/clock-debug.c @@ -0,0 +1,237 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL) version 2 + * Author: Mattias Nilsson <mattias.i.nilsson@stericsson.com> for ST-Ericsson + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/clk.h> +#include <mach/hardware.h> + +#include "clock.h" + +struct clk_debug_info { + struct clk *clk; + struct dentry *dir; + struct dentry *enable; + struct dentry *requests; + int enabled; +}; + +#ifdef CONFIG_DEBUG_FS + +static struct dentry *clk_dir; +static struct dentry *clk_show; +static struct dentry *clk_show_enabled_only; + +static struct clk_debug_info *cdi; +static int num_clks; + +static int clk_show_print(struct seq_file *s, void *p) +{ + int i; + int enabled_only = (int)s->private; + + seq_printf(s, "\n%-20s %10s %s\n", "name", "rate", + "enabled (kernel + debug)"); + for (i = 0; i < num_clks; i++) { + if (enabled_only && !cdi[i].clk->enabled) + continue; + seq_printf(s, + "%-20s %10lu %5d + %d\n", + cdi[i].clk->name, + clk_get_rate(cdi[i].clk), + cdi[i].clk->enabled - cdi[i].enabled, + cdi[i].enabled); + } + + return 0; +} + +static int clk_show_open(struct inode *inode, struct file *file) +{ + return single_open(file, clk_show_print, inode->i_private); +} + +static int clk_enable_print(struct seq_file *s, void *p) +{ + struct clk_debug_info *cdi = s->private; + + return seq_printf(s, "%d\n", cdi->enabled); +} + +static int clk_enable_open(struct inode *inode, struct file *file) +{ + return single_open(file, clk_enable_print, inode->i_private); +} + +static ssize_t clk_enable_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct clk_debug_info *cdi; + long user_val; + int err; + + cdi = ((struct seq_file *)(file->private_data))->private; + + err = kstrtol_from_user(user_buf, count, 0, &user_val); + + if (err) + return err; + + if ((user_val > 0) && (!cdi->enabled)) { + err = clk_enable(cdi->clk); + if (err) { + pr_err("clock: clk_enable(%s) failed.\n", + cdi->clk->name); + return -EFAULT; + } + cdi->enabled = 1; + } else if ((user_val <= 0) && (cdi->enabled)) { + clk_disable(cdi->clk); + cdi->enabled = 0; + } + return count; +} + +static int clk_requests_print(struct seq_file *s, void *p) +{ + struct clk_debug_info *cdi = s->private; + + return seq_printf(s, "%d\n", cdi->clk->enabled); +} + +static int clk_requests_open(struct inode *inode, struct file *file) +{ + return single_open(file, clk_requests_print, inode->i_private); +} + +static const struct file_operations clk_enable_fops = { + .open = clk_enable_open, + .write = clk_enable_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations clk_requests_fops = { + .open = clk_requests_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations clk_show_fops = { + .open = clk_show_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int create_clk_dirs(struct clk_debug_info *cdi, int size) +{ + int i; + + for (i = 0; i < size; i++) { + cdi[i].dir = debugfs_create_dir(cdi[i].clk->name, clk_dir); + if (!cdi[i].dir) + goto no_dir; + } + + for (i = 0; i < size; i++) { + cdi[i].enable = debugfs_create_file("enable", + (S_IRUGO | S_IWUGO), + cdi[i].dir, &cdi[i], + &clk_enable_fops); + if (!cdi[i].enable) + goto no_enable; + } + for (i = 0; i < size; i++) { + cdi[i].requests = debugfs_create_file("requests", S_IRUGO, + cdi[i].dir, &cdi[i], + &clk_requests_fops); + if (!cdi[i].requests) + goto no_requests; + } + return 0; + +no_requests: + while (i--) + debugfs_remove(cdi[i].requests); + i = size; +no_enable: + while (i--) + debugfs_remove(cdi[i].enable); + i = size; +no_dir: + while (i--) + debugfs_remove(cdi[i].dir); + + return -ENOMEM; +} + +int __init dbx500_clk_debug_init(struct clk **clks, int num) +{ + int i; + + cdi = kcalloc(sizeof(struct clk_debug_info), num, GFP_KERNEL); + if (!cdi) + return -ENOMEM; + + for (i = 0; i < num; i++) + cdi[i].clk = clks[i]; + + num_clks = num; + + clk_dir = debugfs_create_dir("clk", NULL); + if (!clk_dir) + goto no_dir; + + clk_show = debugfs_create_file("show", S_IRUGO, clk_dir, (void *)0, + &clk_show_fops); + if (!clk_show) + goto no_show; + + clk_show_enabled_only = debugfs_create_file("show-enabled-only", + S_IRUGO, clk_dir, (void *)1, + &clk_show_fops); + if (!clk_show_enabled_only) + goto no_enabled_only; + + if (create_clk_dirs(cdi, num)) + goto no_clks; + + return 0; + +no_clks: + debugfs_remove(clk_show_enabled_only); +no_enabled_only: + debugfs_remove(clk_show); +no_show: + debugfs_remove(clk_dir); +no_dir: + kfree(cdi); + return -ENOMEM; +} + +static int __init clk_debug_init(void) +{ + if (cpu_is_u8500()) + db8500_clk_debug_init(); + else if (cpu_is_u5500()) + db5500_clk_debug_init(); + + return 0; +} +module_init(clk_debug_init); + +#endif /* CONFIG_DEBUG_FS */ diff --git a/arch/arm/mach-ux500/clock.c b/arch/arm/mach-ux500/clock.c index e832664d1bd..108014ea55b 100644 --- a/arch/arm/mach-ux500/clock.c +++ b/arch/arm/mach-ux500/clock.c @@ -7,815 +7,476 @@ * published by the Free Software Foundation. */ #include <linux/module.h> -#include <linux/kernel.h> -#include <linux/list.h> #include <linux/errno.h> -#include <linux/err.h> -#include <linux/clk.h> #include <linux/io.h> -#include <linux/clkdev.h> -#include <linux/cpufreq.h> +#include <linux/spinlock.h> +#include <linux/mfd/ab8500/sysctrl.h> +#include <linux/mfd/dbx500-prcmu.h> -#include <plat/mtu.h> -#include <mach/hardware.h> #include "clock.h" -#ifdef CONFIG_DEBUG_FS -#include <linux/debugfs.h> -#include <linux/uaccess.h> /* for copy_from_user */ -static LIST_HEAD(clk_list); -#endif +#define PRCC_PCKEN 0x0 +#define PRCC_PCKDIS 0x4 +#define PRCC_KCKEN 0x8 +#define PRCC_KCKDIS 0xC +#define PRCC_PCKSR 0x10 +#define PRCC_KCKSR 0x14 -#define PRCC_PCKEN 0x00 -#define PRCC_PCKDIS 0x04 -#define PRCC_KCKEN 0x08 -#define PRCC_KCKDIS 0x0C - -#define PRCM_YYCLKEN0_MGT_SET 0x510 -#define PRCM_YYCLKEN1_MGT_SET 0x514 -#define PRCM_YYCLKEN0_MGT_CLR 0x518 -#define PRCM_YYCLKEN1_MGT_CLR 0x51C -#define PRCM_YYCLKEN0_MGT_VAL 0x520 -#define PRCM_YYCLKEN1_MGT_VAL 0x524 - -#define PRCM_SVAMMDSPCLK_MGT 0x008 -#define PRCM_SIAMMDSPCLK_MGT 0x00C -#define PRCM_SGACLK_MGT 0x014 -#define PRCM_UARTCLK_MGT 0x018 -#define PRCM_MSP02CLK_MGT 0x01C -#define PRCM_MSP1CLK_MGT 0x288 -#define PRCM_I2CCLK_MGT 0x020 -#define PRCM_SDMMCCLK_MGT 0x024 -#define PRCM_SLIMCLK_MGT 0x028 -#define PRCM_PER1CLK_MGT 0x02C -#define PRCM_PER2CLK_MGT 0x030 -#define PRCM_PER3CLK_MGT 0x034 -#define PRCM_PER5CLK_MGT 0x038 -#define PRCM_PER6CLK_MGT 0x03C -#define PRCM_PER7CLK_MGT 0x040 -#define PRCM_LCDCLK_MGT 0x044 -#define PRCM_BMLCLK_MGT 0x04C -#define PRCM_HSITXCLK_MGT 0x050 -#define PRCM_HSIRXCLK_MGT 0x054 -#define PRCM_HDMICLK_MGT 0x058 -#define PRCM_APEATCLK_MGT 0x05C -#define PRCM_APETRACECLK_MGT 0x060 -#define PRCM_MCDECLK_MGT 0x064 -#define PRCM_IPI2CCLK_MGT 0x068 -#define PRCM_DSIALTCLK_MGT 0x06C -#define PRCM_DMACLK_MGT 0x074 -#define PRCM_B2R2CLK_MGT 0x078 -#define PRCM_TVCLK_MGT 0x07C -#define PRCM_TCR 0x1C8 -#define PRCM_TCR_STOPPED (1 << 16) -#define PRCM_TCR_DOZE_MODE (1 << 17) -#define PRCM_UNIPROCLK_MGT 0x278 -#define PRCM_SSPCLK_MGT 0x280 -#define PRCM_RNGCLK_MGT 0x284 -#define PRCM_UICCCLK_MGT 0x27C - -#define PRCM_MGT_ENABLE (1 << 8) - -static DEFINE_SPINLOCK(clocks_lock); - -static void __clk_enable(struct clk *clk) -{ - if (clk->enabled++ == 0) { - if (clk->parent_cluster) - __clk_enable(clk->parent_cluster); +DEFINE_MUTEX(clk_opp100_mutex); +static DEFINE_SPINLOCK(clk_spin_lock); +#define NO_LOCK &clk_spin_lock - if (clk->parent_periph) - __clk_enable(clk->parent_periph); +static void __iomem *prcmu_base; - if (clk->ops && clk->ops->enable) - clk->ops->enable(clk); +static void __clk_lock(struct clk *clk, void *last_lock, unsigned long *flags) +{ + if (clk->mutex != last_lock) { + if (clk->mutex == NULL) + spin_lock_irqsave(&clk_spin_lock, *flags); + else + mutex_lock(clk->mutex); } } -int clk_enable(struct clk *clk) +static void __clk_unlock(struct clk *clk, void *last_lock, unsigned long flags) +{ + if (clk->mutex != last_lock) { + if (clk->mutex == NULL) + spin_unlock_irqrestore(&clk_spin_lock, flags); + else + mutex_unlock(clk->mutex); + } +} + +void __clk_disable(struct clk *clk, void *current_lock) { unsigned long flags; - spin_lock_irqsave(&clocks_lock, flags); - __clk_enable(clk); - spin_unlock_irqrestore(&clocks_lock, flags); + if (clk == NULL) + return; - return 0; -} -EXPORT_SYMBOL(clk_enable); + __clk_lock(clk, current_lock, &flags); -static void __clk_disable(struct clk *clk) -{ - if (--clk->enabled == 0) { - if (clk->ops && clk->ops->disable) + if (clk->enabled && (--clk->enabled == 0)) { + if ((clk->ops != NULL) && (clk->ops->disable != NULL)) clk->ops->disable(clk); + __clk_disable(clk->parent, clk->mutex); + __clk_disable(clk->bus_parent, clk->mutex); + } - if (clk->parent_periph) - __clk_disable(clk->parent_periph); + __clk_unlock(clk, current_lock, flags); - if (clk->parent_cluster) - __clk_disable(clk->parent_cluster); - } + return; } -void clk_disable(struct clk *clk) +int __clk_enable(struct clk *clk, void *current_lock) { + int err; unsigned long flags; - WARN_ON(!clk->enabled); + if (clk == NULL) + return 0; - spin_lock_irqsave(&clocks_lock, flags); - __clk_disable(clk); - spin_unlock_irqrestore(&clocks_lock, flags); -} -EXPORT_SYMBOL(clk_disable); + __clk_lock(clk, current_lock, &flags); -/* - * The MTU has a separate, rather complex muxing setup - * with alternative parents (peripheral cluster or - * ULP or fixed 32768 Hz) depending on settings - */ -static unsigned long clk_mtu_get_rate(struct clk *clk) -{ - void __iomem *addr; - u32 tcr; - int mtu = (int) clk->data; - /* - * One of these is selected eventually - * TODO: Replace the constant with a reference - * to the ULP source once this is modeled. - */ - unsigned long clk32k = 32768; - unsigned long mturate; - unsigned long retclk; - - if (cpu_is_u5500()) - addr = __io_address(U5500_PRCMU_BASE); - else if (cpu_is_u8500()) - addr = __io_address(U8500_PRCMU_BASE); - else - ux500_unknown_soc(); + if (!clk->enabled) { + err = __clk_enable(clk->bus_parent, clk->mutex); + if (unlikely(err)) + goto bus_parent_error; - /* - * On a startup, always conifgure the TCR to the doze mode; - * bootloaders do it for us. Do this in the kernel too. - */ - writel(PRCM_TCR_DOZE_MODE, addr + PRCM_TCR); + err = __clk_enable(clk->parent, clk->mutex); + if (unlikely(err)) + goto parent_error; - tcr = readl(addr + PRCM_TCR); + if ((clk->ops != NULL) && (clk->ops->enable != NULL)) { + err = clk->ops->enable(clk); + if (unlikely(err)) + goto enable_error; + } + } + clk->enabled++; - /* Get the rate from the parent as a default */ - if (clk->parent_periph) - mturate = clk_get_rate(clk->parent_periph); - else if (clk->parent_cluster) - mturate = clk_get_rate(clk->parent_cluster); - else - /* We need to be connected SOMEWHERE */ - BUG(); + __clk_unlock(clk, current_lock, flags); - /* Return the clock selected for this MTU */ - if (tcr & (1 << mtu)) - retclk = clk32k; - else - retclk = mturate; + return 0; + +enable_error: + __clk_disable(clk->parent, clk->mutex); +parent_error: + __clk_disable(clk->bus_parent, clk->mutex); +bus_parent_error: + + __clk_unlock(clk, current_lock, flags); - pr_info("MTU%d clock rate: %lu Hz\n", mtu, retclk); - return retclk; + return err; } -unsigned long clk_get_rate(struct clk *clk) +unsigned long __clk_get_rate(struct clk *clk, void *current_lock) { unsigned long rate; + unsigned long flags; - /* - * If there is a custom getrate callback for this clock, - * it will take precedence. - */ - if (clk->get_rate) - return clk->get_rate(clk); - - if (clk->ops && clk->ops->get_rate) - return clk->ops->get_rate(clk); - - rate = clk->rate; - if (!rate) { - if (clk->parent_periph) - rate = clk_get_rate(clk->parent_periph); - else if (clk->parent_cluster) - rate = clk_get_rate(clk->parent_cluster); - } + if (clk == NULL) + return 0; + + __clk_lock(clk, current_lock, &flags); + + if ((clk->ops != NULL) && (clk->ops->get_rate != NULL)) + rate = clk->ops->get_rate(clk); + else if (clk->rate) + rate = clk->rate; + else + rate = __clk_get_rate(clk->parent, clk->mutex); + + __clk_unlock(clk, current_lock, flags); return rate; } -EXPORT_SYMBOL(clk_get_rate); -long clk_round_rate(struct clk *clk, unsigned long rate) +static long __clk_round_rate(struct clk *clk, unsigned long rate) { - /*TODO*/ - return rate; + if ((clk->ops != NULL) && (clk->ops->round_rate != NULL)) + return clk->ops->round_rate(clk, rate); + + return -ENOSYS; } -EXPORT_SYMBOL(clk_round_rate); -int clk_set_rate(struct clk *clk, unsigned long rate) +static int __clk_set_rate(struct clk *clk, unsigned long rate) { - clk->rate = rate; - return 0; + if ((clk->ops != NULL) && (clk->ops->set_rate != NULL)) + return clk->ops->set_rate(clk, rate); + + return -ENOSYS; } -EXPORT_SYMBOL(clk_set_rate); -static void clk_prcmu_enable(struct clk *clk) +int clk_enable(struct clk *clk) { - void __iomem *cg_set_reg = __io_address(U8500_PRCMU_BASE) - + PRCM_YYCLKEN0_MGT_SET + clk->prcmu_cg_off; + if (clk == NULL) + return -EINVAL; - writel(1 << clk->prcmu_cg_bit, cg_set_reg); + return __clk_enable(clk, NO_LOCK); } +EXPORT_SYMBOL(clk_enable); -static void clk_prcmu_disable(struct clk *clk) +void clk_disable(struct clk *clk) { - void __iomem *cg_clr_reg = __io_address(U8500_PRCMU_BASE) - + PRCM_YYCLKEN0_MGT_CLR + clk->prcmu_cg_off; - writel(1 << clk->prcmu_cg_bit, cg_clr_reg); + if (clk == NULL) + return; + + WARN_ON(!clk->enabled); + __clk_disable(clk, NO_LOCK); } +EXPORT_SYMBOL(clk_disable); -/* ED doesn't have the combined set/clr registers */ -static void clk_prcmu_ed_enable(struct clk *clk) +unsigned long clk_get_rate(struct clk *clk) { - void __iomem *addr = __io_address(U8500_PRCMU_BASE) - + clk->prcmu_cg_mgt; + if (clk == NULL) + return 0; - writel(readl(addr) | PRCM_MGT_ENABLE, addr); + return __clk_get_rate(clk, NO_LOCK); } +EXPORT_SYMBOL(clk_get_rate); -static void clk_prcmu_ed_disable(struct clk *clk) +long clk_round_rate(struct clk *clk, unsigned long rate) { - void __iomem *addr = __io_address(U8500_PRCMU_BASE) - + clk->prcmu_cg_mgt; + long rounded_rate; + unsigned long flags; + + if (clk == NULL) + return -EINVAL; - writel(readl(addr) & ~PRCM_MGT_ENABLE, addr); + __clk_lock(clk, NO_LOCK, &flags); + + rounded_rate = __clk_round_rate(clk, rate); + + __clk_unlock(clk, NO_LOCK, flags); + + return rounded_rate; } +EXPORT_SYMBOL(clk_round_rate); -static struct clkops clk_prcmu_ops = { - .enable = clk_prcmu_enable, - .disable = clk_prcmu_disable, -}; +long clk_round_rate_rec(struct clk *clk, unsigned long rate) +{ + long rounded_rate; + unsigned long flags; -static unsigned int clkrst_base[] = { - [1] = U8500_CLKRST1_BASE, - [2] = U8500_CLKRST2_BASE, - [3] = U8500_CLKRST3_BASE, - [5] = U8500_CLKRST5_BASE, - [6] = U8500_CLKRST6_BASE, - [7] = U8500_CLKRST7_BASE_ED, -}; + if ((clk == NULL) || (clk->parent == NULL)) + return -EINVAL; + + __clk_lock(clk->parent, clk->mutex, &flags); + + rounded_rate = __clk_round_rate(clk->parent, rate); + + __clk_unlock(clk->parent, clk->mutex, flags); -static void clk_prcc_enable(struct clk *clk) + return rounded_rate; +} + +int clk_set_rate(struct clk *clk, unsigned long rate) { - void __iomem *addr = __io_address(clkrst_base[clk->cluster]); + int err; + unsigned long flags; - if (clk->prcc_kernel != -1) - writel(1 << clk->prcc_kernel, addr + PRCC_KCKEN); + if (clk == NULL) + return -EINVAL; - if (clk->prcc_bus != -1) - writel(1 << clk->prcc_bus, addr + PRCC_PCKEN); + __clk_lock(clk, NO_LOCK, &flags); + + err = __clk_set_rate(clk, rate); + + __clk_unlock(clk, NO_LOCK, flags); + + return err; } +EXPORT_SYMBOL(clk_set_rate); -static void clk_prcc_disable(struct clk *clk) +int clk_set_rate_rec(struct clk *clk, unsigned long rate) { - void __iomem *addr = __io_address(clkrst_base[clk->cluster]); + int err; + unsigned long flags; + + if ((clk == NULL) || (clk->parent == NULL)) + return -EINVAL; + + __clk_lock(clk->parent, clk->mutex, &flags); + + err = __clk_set_rate(clk->parent, rate); - if (clk->prcc_bus != -1) - writel(1 << clk->prcc_bus, addr + PRCC_PCKDIS); + __clk_unlock(clk->parent, clk->mutex, flags); - if (clk->prcc_kernel != -1) - writel(1 << clk->prcc_kernel, addr + PRCC_KCKDIS); + return err; } -static struct clkops clk_prcc_ops = { - .enable = clk_prcc_enable, - .disable = clk_prcc_disable, -}; +int clk_set_parent(struct clk *clk, struct clk *parent) +{ + int err = 0; + unsigned long flags; + struct clk **p; -static struct clk clk_32khz = { - .name = "clk_32khz", - .rate = 32000, -}; + if ((clk == NULL) || (clk->parents == NULL)) + return -EINVAL; + for (p = clk->parents; *p != parent; p++) { + if (*p == NULL) /* invalid parent */ + return -EINVAL; + } -/* - * PRCMU level clock gating - */ + __clk_lock(clk, NO_LOCK, &flags); -/* Bank 0 */ -static DEFINE_PRCMU_CLK(svaclk, 0x0, 2, SVAMMDSPCLK); -static DEFINE_PRCMU_CLK(siaclk, 0x0, 3, SIAMMDSPCLK); -static DEFINE_PRCMU_CLK(sgaclk, 0x0, 4, SGACLK); -static DEFINE_PRCMU_CLK_RATE(uartclk, 0x0, 5, UARTCLK, 38400000); -static DEFINE_PRCMU_CLK(msp02clk, 0x0, 6, MSP02CLK); -static DEFINE_PRCMU_CLK(msp1clk, 0x0, 7, MSP1CLK); /* v1 */ -static DEFINE_PRCMU_CLK_RATE(i2cclk, 0x0, 8, I2CCLK, 48000000); -static DEFINE_PRCMU_CLK_RATE(sdmmcclk, 0x0, 9, SDMMCCLK, 100000000); -static DEFINE_PRCMU_CLK(slimclk, 0x0, 10, SLIMCLK); -static DEFINE_PRCMU_CLK(per1clk, 0x0, 11, PER1CLK); -static DEFINE_PRCMU_CLK(per2clk, 0x0, 12, PER2CLK); -static DEFINE_PRCMU_CLK(per3clk, 0x0, 13, PER3CLK); -static DEFINE_PRCMU_CLK(per5clk, 0x0, 14, PER5CLK); -static DEFINE_PRCMU_CLK_RATE(per6clk, 0x0, 15, PER6CLK, 133330000); -static DEFINE_PRCMU_CLK_RATE(per7clk, 0x0, 16, PER7CLK, 100000000); -static DEFINE_PRCMU_CLK(lcdclk, 0x0, 17, LCDCLK); -static DEFINE_PRCMU_CLK(bmlclk, 0x0, 18, BMLCLK); -static DEFINE_PRCMU_CLK(hsitxclk, 0x0, 19, HSITXCLK); -static DEFINE_PRCMU_CLK(hsirxclk, 0x0, 20, HSIRXCLK); -static DEFINE_PRCMU_CLK(hdmiclk, 0x0, 21, HDMICLK); -static DEFINE_PRCMU_CLK(apeatclk, 0x0, 22, APEATCLK); -static DEFINE_PRCMU_CLK(apetraceclk, 0x0, 23, APETRACECLK); -static DEFINE_PRCMU_CLK(mcdeclk, 0x0, 24, MCDECLK); -static DEFINE_PRCMU_CLK(ipi2clk, 0x0, 25, IPI2CCLK); -static DEFINE_PRCMU_CLK(dsialtclk, 0x0, 26, DSIALTCLK); /* v1 */ -static DEFINE_PRCMU_CLK(dmaclk, 0x0, 27, DMACLK); -static DEFINE_PRCMU_CLK(b2r2clk, 0x0, 28, B2R2CLK); -static DEFINE_PRCMU_CLK(tvclk, 0x0, 29, TVCLK); -static DEFINE_PRCMU_CLK(uniproclk, 0x0, 30, UNIPROCLK); /* v1 */ -static DEFINE_PRCMU_CLK_RATE(sspclk, 0x0, 31, SSPCLK, 48000000); /* v1 */ - -/* Bank 1 */ -static DEFINE_PRCMU_CLK(rngclk, 0x4, 0, RNGCLK); /* v1 */ -static DEFINE_PRCMU_CLK(uiccclk, 0x4, 1, UICCCLK); /* v1 */ + if ((clk->ops != NULL) && (clk->ops->set_parent != NULL)) { + err = clk->ops->set_parent(clk, parent); + if (err) + goto unlock_and_return; + } else if (clk->enabled) { + err = __clk_enable(parent, clk->mutex); + if (err) + goto unlock_and_return; + __clk_disable(clk->parent, clk->mutex); + } -/* - * PRCC level clock gating - * Format: per#, clk, PCKEN bit, KCKEN bit, parent - */ + clk->parent = parent; -/* Peripheral Cluster #1 */ -static DEFINE_PRCC_CLK(1, i2c4, 10, 9, &clk_i2cclk); -static DEFINE_PRCC_CLK(1, gpio0, 9, -1, NULL); -static DEFINE_PRCC_CLK(1, slimbus0, 8, 8, &clk_slimclk); -static DEFINE_PRCC_CLK(1, spi3_ed, 7, 7, NULL); -static DEFINE_PRCC_CLK(1, spi3_v1, 7, -1, NULL); -static DEFINE_PRCC_CLK(1, i2c2, 6, 6, &clk_i2cclk); -static DEFINE_PRCC_CLK(1, sdi0, 5, 5, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(1, msp1_ed, 4, 4, &clk_msp02clk); -static DEFINE_PRCC_CLK(1, msp1_v1, 4, 4, &clk_msp1clk); -static DEFINE_PRCC_CLK(1, msp0, 3, 3, &clk_msp02clk); -static DEFINE_PRCC_CLK(1, i2c1, 2, 2, &clk_i2cclk); -static DEFINE_PRCC_CLK(1, uart1, 1, 1, &clk_uartclk); -static DEFINE_PRCC_CLK(1, uart0, 0, 0, &clk_uartclk); - -/* Peripheral Cluster #2 */ - -static DEFINE_PRCC_CLK(2, gpio1_ed, 12, -1, NULL); -static DEFINE_PRCC_CLK(2, ssitx_ed, 11, -1, NULL); -static DEFINE_PRCC_CLK(2, ssirx_ed, 10, -1, NULL); -static DEFINE_PRCC_CLK(2, spi0_ed, 9, -1, NULL); -static DEFINE_PRCC_CLK(2, sdi3_ed, 8, 6, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, sdi1_ed, 7, 5, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, msp2_ed, 6, 4, &clk_msp02clk); -static DEFINE_PRCC_CLK(2, sdi4_ed, 4, 2, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, pwl_ed, 3, 1, NULL); -static DEFINE_PRCC_CLK(2, spi1_ed, 2, -1, NULL); -static DEFINE_PRCC_CLK(2, spi2_ed, 1, -1, NULL); -static DEFINE_PRCC_CLK(2, i2c3_ed, 0, 0, &clk_i2cclk); - -static DEFINE_PRCC_CLK(2, gpio1_v1, 11, -1, NULL); -static DEFINE_PRCC_CLK(2, ssitx_v1, 10, 7, NULL); -static DEFINE_PRCC_CLK(2, ssirx_v1, 9, 6, NULL); -static DEFINE_PRCC_CLK(2, spi0_v1, 8, -1, NULL); -static DEFINE_PRCC_CLK(2, sdi3_v1, 7, 5, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, sdi1_v1, 6, 4, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, msp2_v1, 5, 3, &clk_msp02clk); -static DEFINE_PRCC_CLK(2, sdi4_v1, 4, 2, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(2, pwl_v1, 3, 1, NULL); -static DEFINE_PRCC_CLK(2, spi1_v1, 2, -1, NULL); -static DEFINE_PRCC_CLK(2, spi2_v1, 1, -1, NULL); -static DEFINE_PRCC_CLK(2, i2c3_v1, 0, 0, &clk_i2cclk); - -/* Peripheral Cluster #3 */ -static DEFINE_PRCC_CLK(3, gpio2, 8, -1, NULL); -static DEFINE_PRCC_CLK(3, sdi5, 7, 7, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(3, uart2, 6, 6, &clk_uartclk); -static DEFINE_PRCC_CLK(3, ske, 5, 5, &clk_32khz); -static DEFINE_PRCC_CLK(3, sdi2, 4, 4, &clk_sdmmcclk); -static DEFINE_PRCC_CLK(3, i2c0, 3, 3, &clk_i2cclk); -static DEFINE_PRCC_CLK(3, ssp1_ed, 2, 2, &clk_i2cclk); -static DEFINE_PRCC_CLK(3, ssp0_ed, 1, 1, &clk_i2cclk); -static DEFINE_PRCC_CLK(3, ssp1_v1, 2, 2, &clk_sspclk); -static DEFINE_PRCC_CLK(3, ssp0_v1, 1, 1, &clk_sspclk); -static DEFINE_PRCC_CLK(3, fsmc, 0, -1, NULL); - -/* Peripheral Cluster #4 is in the always on domain */ - -/* Peripheral Cluster #5 */ -static DEFINE_PRCC_CLK(5, gpio3, 1, -1, NULL); -static DEFINE_PRCC_CLK(5, usb_ed, 0, 0, &clk_i2cclk); -static DEFINE_PRCC_CLK(5, usb_v1, 0, 0, NULL); - -/* Peripheral Cluster #6 */ - -/* MTU ID in data */ -static DEFINE_PRCC_CLK_CUSTOM(6, mtu1_v1, 8, -1, NULL, clk_mtu_get_rate, 1); -static DEFINE_PRCC_CLK_CUSTOM(6, mtu0_v1, 7, -1, NULL, clk_mtu_get_rate, 0); -static DEFINE_PRCC_CLK(6, cfgreg_v1, 6, 6, NULL); -static DEFINE_PRCC_CLK(6, dmc_ed, 6, 6, NULL); -static DEFINE_PRCC_CLK(6, hash1, 5, -1, NULL); -static DEFINE_PRCC_CLK(6, unipro_v1, 4, 1, &clk_uniproclk); -static DEFINE_PRCC_CLK(6, cryp1_ed, 4, -1, NULL); -static DEFINE_PRCC_CLK(6, pka, 3, -1, NULL); -static DEFINE_PRCC_CLK(6, hash0, 2, -1, NULL); -static DEFINE_PRCC_CLK(6, cryp0, 1, -1, NULL); -static DEFINE_PRCC_CLK(6, rng_ed, 0, 0, &clk_i2cclk); -static DEFINE_PRCC_CLK(6, rng_v1, 0, 0, &clk_rngclk); - -/* Peripheral Cluster #7 */ - -static DEFINE_PRCC_CLK(7, tzpc0_ed, 4, -1, NULL); -/* MTU ID in data */ -static DEFINE_PRCC_CLK_CUSTOM(7, mtu1_ed, 3, -1, NULL, clk_mtu_get_rate, 1); -static DEFINE_PRCC_CLK_CUSTOM(7, mtu0_ed, 2, -1, NULL, clk_mtu_get_rate, 0); -static DEFINE_PRCC_CLK(7, wdg_ed, 1, -1, NULL); -static DEFINE_PRCC_CLK(7, cfgreg_ed, 0, -1, NULL); - -static struct clk clk_dummy_apb_pclk = { - .name = "apb_pclk", -}; +unlock_and_return: + __clk_unlock(clk, NO_LOCK, flags); -static struct clk_lookup u8500_common_clks[] = { - CLK(dummy_apb_pclk, NULL, "apb_pclk"), - - /* Peripheral Cluster #1 */ - CLK(gpio0, "gpio.0", NULL), - CLK(gpio0, "gpio.1", NULL), - CLK(slimbus0, "slimbus0", NULL), - CLK(i2c2, "nmk-i2c.2", NULL), - CLK(sdi0, "sdi0", NULL), - CLK(msp0, "msp0", NULL), - CLK(i2c1, "nmk-i2c.1", NULL), - CLK(uart1, "uart1", NULL), - CLK(uart0, "uart0", NULL), - - /* Peripheral Cluster #3 */ - CLK(gpio2, "gpio.2", NULL), - CLK(gpio2, "gpio.3", NULL), - CLK(gpio2, "gpio.4", NULL), - CLK(gpio2, "gpio.5", NULL), - CLK(sdi5, "sdi5", NULL), - CLK(uart2, "uart2", NULL), - CLK(ske, "ske", NULL), - CLK(ske, "nmk-ske-keypad", NULL), - CLK(sdi2, "sdi2", NULL), - CLK(i2c0, "nmk-i2c.0", NULL), - CLK(fsmc, "fsmc", NULL), - - /* Peripheral Cluster #5 */ - CLK(gpio3, "gpio.8", NULL), - - /* Peripheral Cluster #6 */ - CLK(hash1, "hash1", NULL), - CLK(pka, "pka", NULL), - CLK(hash0, "hash0", NULL), - CLK(cryp0, "cryp0", NULL), - - /* PRCMU level clock gating */ - - /* Bank 0 */ - CLK(svaclk, "sva", NULL), - CLK(siaclk, "sia", NULL), - CLK(sgaclk, "sga", NULL), - CLK(slimclk, "slim", NULL), - CLK(lcdclk, "lcd", NULL), - CLK(bmlclk, "bml", NULL), - CLK(hsitxclk, "stm-hsi.0", NULL), - CLK(hsirxclk, "stm-hsi.1", NULL), - CLK(hdmiclk, "hdmi", NULL), - CLK(apeatclk, "apeat", NULL), - CLK(apetraceclk, "apetrace", NULL), - CLK(mcdeclk, "mcde", NULL), - CLK(ipi2clk, "ipi2", NULL), - CLK(dmaclk, "dma40.0", NULL), - CLK(b2r2clk, "b2r2", NULL), - CLK(tvclk, "tv", NULL), -}; + return err; +} -static struct clk_lookup u8500_ed_clks[] = { - /* Peripheral Cluster #1 */ - CLK(spi3_ed, "spi3", NULL), - CLK(msp1_ed, "msp1", NULL), - - /* Peripheral Cluster #2 */ - CLK(gpio1_ed, "gpio.6", NULL), - CLK(gpio1_ed, "gpio.7", NULL), - CLK(ssitx_ed, "ssitx", NULL), - CLK(ssirx_ed, "ssirx", NULL), - CLK(spi0_ed, "spi0", NULL), - CLK(sdi3_ed, "sdi3", NULL), - CLK(sdi1_ed, "sdi1", NULL), - CLK(msp2_ed, "msp2", NULL), - CLK(sdi4_ed, "sdi4", NULL), - CLK(pwl_ed, "pwl", NULL), - CLK(spi1_ed, "spi1", NULL), - CLK(spi2_ed, "spi2", NULL), - CLK(i2c3_ed, "nmk-i2c.3", NULL), - - /* Peripheral Cluster #3 */ - CLK(ssp1_ed, "ssp1", NULL), - CLK(ssp0_ed, "ssp0", NULL), - - /* Peripheral Cluster #5 */ - CLK(usb_ed, "musb-ux500.0", "usb"), - - /* Peripheral Cluster #6 */ - CLK(dmc_ed, "dmc", NULL), - CLK(cryp1_ed, "cryp1", NULL), - CLK(rng_ed, "rng", NULL), - - /* Peripheral Cluster #7 */ - CLK(tzpc0_ed, "tzpc0", NULL), - CLK(mtu1_ed, "mtu1", NULL), - CLK(mtu0_ed, "mtu0", NULL), - CLK(wdg_ed, "wdg", NULL), - CLK(cfgreg_ed, "cfgreg", NULL), -}; +/* PRCMU clock operations. */ -static struct clk_lookup u8500_v1_clks[] = { - /* Peripheral Cluster #1 */ - CLK(i2c4, "nmk-i2c.4", NULL), - CLK(spi3_v1, "spi3", NULL), - CLK(msp1_v1, "msp1", NULL), - - /* Peripheral Cluster #2 */ - CLK(gpio1_v1, "gpio.6", NULL), - CLK(gpio1_v1, "gpio.7", NULL), - CLK(ssitx_v1, "ssitx", NULL), - CLK(ssirx_v1, "ssirx", NULL), - CLK(spi0_v1, "spi0", NULL), - CLK(sdi3_v1, "sdi3", NULL), - CLK(sdi1_v1, "sdi1", NULL), - CLK(msp2_v1, "msp2", NULL), - CLK(sdi4_v1, "sdi4", NULL), - CLK(pwl_v1, "pwl", NULL), - CLK(spi1_v1, "spi1", NULL), - CLK(spi2_v1, "spi2", NULL), - CLK(i2c3_v1, "nmk-i2c.3", NULL), - - /* Peripheral Cluster #3 */ - CLK(ssp1_v1, "ssp1", NULL), - CLK(ssp0_v1, "ssp0", NULL), - - /* Peripheral Cluster #5 */ - CLK(usb_v1, "musb-ux500.0", "usb"), - - /* Peripheral Cluster #6 */ - CLK(mtu1_v1, "mtu1", NULL), - CLK(mtu0_v1, "mtu0", NULL), - CLK(cfgreg_v1, "cfgreg", NULL), - CLK(hash1, "hash1", NULL), - CLK(unipro_v1, "unipro", NULL), - CLK(rng_v1, "rng", NULL), - - /* PRCMU level clock gating */ - - /* Bank 0 */ - CLK(uniproclk, "uniproclk", NULL), - CLK(dsialtclk, "dsialt", NULL), - - /* Bank 1 */ - CLK(rngclk, "rng", NULL), - CLK(uiccclk, "uicc", NULL), -}; +static int prcmu_clk_enable(struct clk *clk) +{ + return prcmu_request_clock(clk->cg_sel, true); +} -#ifdef CONFIG_DEBUG_FS -/* - * debugfs support to trace clock tree hierarchy and attributes with - * powerdebug - */ -static struct dentry *clk_debugfs_root; +static void prcmu_clk_disable(struct clk *clk) +{ + if (prcmu_request_clock(clk->cg_sel, false)) { + pr_err("clock: %s failed to disable %s.\n", __func__, + clk->name); + } +} -void __init clk_debugfs_add_table(struct clk_lookup *cl, size_t num) +static int request_ape_opp100(bool enable) { - while (num--) { - /* Check that the clock has not been already registered */ - if (!(cl->clk->list.prev != cl->clk->list.next)) - list_add_tail(&cl->clk->list, &clk_list); + static unsigned int requests; + + if (enable) { + if (0 == requests++) { + return prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, + "clock", 100); + } + } else if (1 == requests--) { + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "clock"); + } + return 0; +} - cl++; +static int prcmu_opp100_clk_enable(struct clk *clk) +{ + int r; + + r = request_ape_opp100(true); + if (r) { + pr_err("clock: %s failed to request APE OPP 100%% for %s.\n", + __func__, clk->name); + return r; } + return prcmu_request_clock(clk->cg_sel, true); } -static ssize_t usecount_dbg_read(struct file *file, char __user *buf, - size_t size, loff_t *off) +static void prcmu_opp100_clk_disable(struct clk *clk) { - struct clk *clk = file->f_dentry->d_inode->i_private; - char cusecount[128]; - unsigned int len; + if (prcmu_request_clock(clk->cg_sel, false)) + goto out_error; + if (request_ape_opp100(false)) + goto out_error; + return; + +out_error: + pr_err("clock: %s failed to disable %s.\n", __func__, clk->name); +} - len = sprintf(cusecount, "%u\n", clk->enabled); - return simple_read_from_buffer(buf, size, off, cusecount, len); +static unsigned long prcmu_clk_get_rate(struct clk *clk) +{ + return prcmu_clock_rate(clk->cg_sel); } -static ssize_t rate_dbg_read(struct file *file, char __user *buf, - size_t size, loff_t *off) +static long prcmu_clk_round_rate(struct clk *clk, unsigned long rate) { - struct clk *clk = file->f_dentry->d_inode->i_private; - char crate[128]; - unsigned int rate; - unsigned int len; - - rate = clk_get_rate(clk); - len = sprintf(crate, "%u\n", rate); - return simple_read_from_buffer(buf, size, off, crate, len); + return prcmu_round_clock_rate(clk->cg_sel, rate); } -static const struct file_operations usecount_fops = { - .read = usecount_dbg_read, +static int prcmu_clk_set_rate(struct clk *clk, unsigned long rate) +{ + return prcmu_set_clock_rate(clk->cg_sel, rate); +} + +struct clkops prcmu_clk_ops = { + .enable = prcmu_clk_enable, + .disable = prcmu_clk_disable, + .get_rate = prcmu_clk_get_rate, }; -static const struct file_operations set_rate_fops = { - .read = rate_dbg_read, +struct clkops prcmu_scalable_clk_ops = { + .enable = prcmu_clk_enable, + .disable = prcmu_clk_disable, + .get_rate = prcmu_clk_get_rate, + .round_rate = prcmu_clk_round_rate, + .set_rate = prcmu_clk_set_rate, }; -static struct dentry *clk_debugfs_register_dir(struct clk *c, - struct dentry *p_dentry) -{ - struct dentry *d, *clk_d; - const char *p = c->name; - - if (!p) - p = "BUG"; - - clk_d = debugfs_create_dir(p, p_dentry); - if (!clk_d) - return NULL; - - d = debugfs_create_file("usecount", S_IRUGO, - clk_d, c, &usecount_fops); - if (!d) - goto err_out; - d = debugfs_create_file("rate", S_IRUGO, - clk_d, c, &set_rate_fops); - if (!d) - goto err_out; - /* - * TODO : not currently available in ux500 - * d = debugfs_create_x32("flags", S_IRUGO, clk_d, (u32 *)&c->flags); - * if (!d) - * goto err_out; - */ - - return clk_d; - -err_out: - debugfs_remove_recursive(clk_d); - return NULL; -} +struct clkops prcmu_opp100_clk_ops = { + .enable = prcmu_opp100_clk_enable, + .disable = prcmu_opp100_clk_disable, + .get_rate = prcmu_clk_get_rate, +}; + +/* PRCC clock operations. */ -static int clk_debugfs_register_one(struct clk *c) +static int prcc_pclk_enable(struct clk *clk) { - struct clk *pa = c->parent_periph; - struct clk *bpa = c->parent_cluster; - - if (!(bpa && !pa)) { - c->dent = clk_debugfs_register_dir(c, - pa ? pa->dent : clk_debugfs_root); - if (!c->dent) - return -ENOMEM; - } + void __iomem *io_base = __io_address(clk->io_base); - if (bpa) { - c->dent_bus = clk_debugfs_register_dir(c, - bpa->dent_bus ? bpa->dent_bus : bpa->dent); - if ((!c->dent_bus) && (c->dent)) { - debugfs_remove_recursive(c->dent); - c->dent = NULL; - return -ENOMEM; - } - } + writel(clk->cg_sel, (io_base + PRCC_PCKEN)); + while (!(readl(io_base + PRCC_PCKSR) & clk->cg_sel)) + cpu_relax(); return 0; } -static int clk_debugfs_register(struct clk *c) +static void prcc_pclk_disable(struct clk *clk) { - int err; - struct clk *pa = c->parent_periph; - struct clk *bpa = c->parent_cluster; - - if (pa && (!pa->dent && !pa->dent_bus)) { - err = clk_debugfs_register(pa); - if (err) - return err; - } + void __iomem *io_base = __io_address(clk->io_base); - if (bpa && (!bpa->dent && !bpa->dent_bus)) { - err = clk_debugfs_register(bpa); - if (err) - return err; - } - - if ((!c->dent) && (!c->dent_bus)) { - err = clk_debugfs_register_one(c); - if (err) - return err; - } - return 0; + writel(clk->cg_sel, (io_base + PRCC_PCKDIS)); } -static int __init clk_debugfs_init(void) +struct clkops prcc_pclk_ops = { + .enable = prcc_pclk_enable, + .disable = prcc_pclk_disable, +}; + +static int prcc_kclk_enable(struct clk *clk) { - struct clk *c; - struct dentry *d; int err; + void __iomem *io_base = __io_address(clk->io_base); - d = debugfs_create_dir("clock", NULL); - if (!d) - return -ENOMEM; - clk_debugfs_root = d; + err = __clk_enable(clk->clock, clk->mutex); + if (err) + return err; - list_for_each_entry(c, &clk_list, list) { - err = clk_debugfs_register(c); - if (err) - goto err_out; - } - return 0; -err_out: - debugfs_remove_recursive(clk_debugfs_root); - return err; -} + writel(clk->cg_sel, (io_base + PRCC_KCKEN)); + while (!(readl(io_base + PRCC_KCKSR) & clk->cg_sel)) + cpu_relax(); -late_initcall(clk_debugfs_init); -#endif /* defined(CONFIG_DEBUG_FS) */ + __clk_disable(clk->clock, clk->mutex); -unsigned long clk_smp_twd_rate = 400000000; + return 0; +} -unsigned long clk_smp_twd_get_rate(struct clk *clk) +static void prcc_kclk_disable(struct clk *clk) { - return clk_smp_twd_rate; + void __iomem *io_base = __io_address(clk->io_base); + + (void)__clk_enable(clk->clock, clk->mutex); + writel(clk->cg_sel, (io_base + PRCC_KCKDIS)); + __clk_disable(clk->clock, clk->mutex); } -static struct clk clk_smp_twd = { - .get_rate = clk_smp_twd_get_rate, - .name = "smp_twd", +struct clkops prcc_kclk_ops = { + .enable = prcc_kclk_enable, + .disable = prcc_kclk_disable, }; -static struct clk_lookup clk_smp_twd_lookup = { - .dev_id = "smp_twd", - .clk = &clk_smp_twd, +struct clkops prcc_kclk_rec_ops = { + .enable = prcc_kclk_enable, + .disable = prcc_kclk_disable, + .round_rate = clk_round_rate_rec, + .set_rate = clk_set_rate_rec, }; #ifdef CONFIG_CPU_FREQ +extern unsigned long dbx500_cpufreq_getfreq(void); -static int clk_twd_cpufreq_transition(struct notifier_block *nb, - unsigned long state, void *data) +unsigned long clk_smp_twd_get_rate(struct clk *clk) { - struct cpufreq_freqs *f = data; - - if (state == CPUFREQ_PRECHANGE) { - /* Save frequency in simple Hz */ - clk_smp_twd_rate = f->new * 1000; - } - - return NOTIFY_OK; + return dbx500_cpufreq_getfreq() / 2; } -static struct notifier_block clk_twd_cpufreq_nb = { - .notifier_call = clk_twd_cpufreq_transition, +static struct clkops clk_smp_twd_ops = { + .get_rate = clk_smp_twd_get_rate, }; -static int clk_init_smp_twd_cpufreq(void) -{ - return cpufreq_register_notifier(&clk_twd_cpufreq_nb, - CPUFREQ_TRANSITION_NOTIFIER); -} -late_initcall(clk_init_smp_twd_cpufreq); +static struct clk clk_smp_twd = { + .name = "smp_twd", + .ops = &clk_smp_twd_ops, +}; +static struct clk_lookup clk_smp_twd_lookup = { + .clk = &clk_smp_twd, + .dev_id = "smp_twd", +}; #endif int __init clk_init(void) { - if (cpu_is_u8500ed()) { - clk_prcmu_ops.enable = clk_prcmu_ed_enable; - clk_prcmu_ops.disable = clk_prcmu_ed_disable; - clk_per6clk.rate = 100000000; + if (cpu_is_u8500()) { + prcmu_base = __io_address(U8500_PRCMU_BASE); } else if (cpu_is_u5500()) { - /* Clock tree for U5500 not implemented yet */ - clk_prcc_ops.enable = clk_prcc_ops.disable = NULL; - clk_prcmu_ops.enable = clk_prcmu_ops.disable = NULL; - clk_uartclk.rate = 36360000; - clk_sdmmcclk.rate = 99900000; + prcmu_base = __io_address(U5500_PRCMU_BASE); + } else { + pr_err("clock: Unknown DB Asic.\n"); + return -EIO; } - clkdev_add_table(u8500_common_clks, ARRAY_SIZE(u8500_common_clks)); - if (cpu_is_u8500ed()) - clkdev_add_table(u8500_ed_clks, ARRAY_SIZE(u8500_ed_clks)); - else - clkdev_add_table(u8500_v1_clks, ARRAY_SIZE(u8500_v1_clks)); + if (cpu_is_u8500()) + db8500_clk_init(); + else if (cpu_is_u5500()) + db5500_clk_init(); +#ifdef CONFIG_CPU_FREQ clkdev_add(&clk_smp_twd_lookup); - -#ifdef CONFIG_DEBUG_FS - clk_debugfs_add_table(u8500_common_clks, ARRAY_SIZE(u8500_common_clks)); - if (cpu_is_u8500ed()) - clk_debugfs_add_table(u8500_ed_clks, ARRAY_SIZE(u8500_ed_clks)); - else - clk_debugfs_add_table(u8500_v1_clks, ARRAY_SIZE(u8500_v1_clks)); #endif + return 0; } diff --git a/arch/arm/mach-ux500/clock.h b/arch/arm/mach-ux500/clock.h index 07449070522..39d8a61d79b 100644 --- a/arch/arm/mach-ux500/clock.h +++ b/arch/arm/mach-ux500/clock.h @@ -1,11 +1,55 @@ /* - * Copyright (C) 2010 ST-Ericsson + * Copyright (C) 2010 ST-Ericsson SA * Copyright (C) 2009 STMicroelectronics * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ +#ifndef UX500_CLOCK_H +#define UX500_CLOCK_H + +#include <linux/clkdev.h> + +/** + * struct clk + * @ops: The hardware specific operations defined for the clock. + * @name: The name of the clock. + * @mutex: The mutex to lock when operating on the clock. %NULL means that + * the common clock spinlock will be used. + * @enabled: A reference counter of the enable requests for the clock. + * @opp100: A flag saying whether the clock is requested to run at the + * OPP 100%% frequency. + * @rate: The frequency of the clock. For scalable and scaling clocks, + * this is the OPP 100%% frequency. + * @io_base: An IO memory base address, meaningful only when considered + * together with the defined @ops. + * @cg_sel: Clock gate selector, meaningful only when considered together + * with the specified @ops. + * @parent: The current (or only) parent clock of the clock. + * @bus_parent: The (optional) auxiliary bus clock "parent" of the clock. + * @parents: A list of the possible parents the clock can have. This should + * be a %NULL-terminated &struct_clk array. Present if and only + * if clk_set_parent() is implemented for the clock. + * @regulator: The regulator needed to have the clock functional, if any. + * @clock: The clock needed to control the clock, if any. + */ +struct clk { + const struct clkops *ops; + const char *name; + struct mutex *mutex; + unsigned int enabled; + bool opp100; + unsigned long rate; + unsigned int io_base; + u32 cg_sel; + struct clk *parent; + struct clk *bus_parent; + struct clk **parents; + struct regulator *regulator; + struct clk *clock; + struct list_head list; +}; /** * struct clkops - ux500 clock operations @@ -18,134 +62,119 @@ * NULL, the rate in the struct clk will be used. */ struct clkops { - void (*enable) (struct clk *); - void (*disable) (struct clk *); - unsigned long (*get_rate) (struct clk *); + int (*enable)(struct clk *); + void (*disable)(struct clk *); + unsigned long (*get_rate)(struct clk *); + int (*set_rate)(struct clk *, unsigned long); + long (*round_rate)(struct clk *, unsigned long); + int (*set_parent)(struct clk *, struct clk *); }; -/** - * struct clk - ux500 clock structure - * @ops: pointer to clkops struct used to control this clock - * @name: name, for debugging - * @enabled: refcount. positive if enabled, zero if disabled - * @get_rate: custom callback for getting the clock rate - * @data: custom per-clock data for example for the get_rate - * callback - * @rate: fixed rate for clocks which don't implement - * ops->getrate - * @prcmu_cg_off: address offset of the combined enable/disable register - * (used on u8500v1) - * @prcmu_cg_bit: bit in the combined enable/disable register (used on - * u8500v1) - * @prcmu_cg_mgt: address of the enable/disable register (used on - * u8500ed) - * @cluster: peripheral cluster number - * @prcc_bus: bit for the bus clock in the peripheral's CLKRST - * @prcc_kernel: bit for the kernel clock in the peripheral's CLKRST. - * -1 if no kernel clock exists. - * @parent_cluster: pointer to parent's cluster clk struct - * @parent_periph: pointer to parent's peripheral clk struct - * - * Peripherals are organised into clusters, and each cluster has an associated - * bus clock. Some peripherals also have a parent peripheral clock. - * - * In order to enable a clock for a peripheral, we need to enable: - * (1) the parent cluster (bus) clock at the PRCMU level - * (2) the parent peripheral clock (if any) at the PRCMU level - * (3) the peripheral's bus & kernel clock at the PRCC level - * - * (1) and (2) are handled by defining clk structs (DEFINE_PRCMU_CLK) for each - * of the cluster and peripheral clocks, and hooking these as the parents of - * the individual peripheral clocks. - * - * (3) is handled by specifying the bits in the PRCC control registers required - * to enable these clocks and modifying them in the ->enable and - * ->disable callbacks of the peripheral clocks (DEFINE_PRCC_CLK). - * - * This structure describes both the PRCMU-level clocks and PRCC-level clocks. - * The prcmu_* fields are only used for the PRCMU clocks, and the cluster, - * prcc, and parent pointers are only used for the PRCC-level clocks. - */ -struct clk { - const struct clkops *ops; - const char *name; - unsigned int enabled; - unsigned long (*get_rate)(struct clk *); - void *data; - - unsigned long rate; - struct list_head list; +extern struct clkops prcmu_clk_ops; +extern struct clkops prcmu_scalable_clk_ops; +extern struct clkops prcmu_opp100_clk_ops; +extern struct mutex clk_opp100_mutex; +extern struct clkops prcc_pclk_ops; +extern struct clkops prcc_kclk_ops; +extern struct clkops prcc_kclk_rec_ops; +extern struct clkops sga_clk_ops; - /* These three are only for PRCMU clks */ +#define CLK_LOOKUP(_clk, _dev_id, _con_id) \ + { .dev_id = _dev_id, .con_id = _con_id, .clk = &_clk } - unsigned int prcmu_cg_off; - unsigned int prcmu_cg_bit; - unsigned int prcmu_cg_mgt; - - /* The rest are only for PRCC clks */ - - int cluster; - unsigned int prcc_bus; - unsigned int prcc_kernel; - - struct clk *parent_cluster; - struct clk *parent_periph; -#if defined(CONFIG_DEBUG_FS) - struct dentry *dent; /* For visible tree hierarchy */ - struct dentry *dent_bus; /* For visible tree hierarchy */ -#endif -}; +/* Define PRCMU Clock */ +#define DEF_PRCMU_CLK(_name, _cg_sel, _rate) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcmu_clk_ops, \ + .cg_sel = _cg_sel, \ + .rate = _rate, \ + } -#define DEFINE_PRCMU_CLK(_name, _cg_off, _cg_bit, _reg) \ -struct clk clk_##_name = { \ - .name = #_name, \ - .ops = &clk_prcmu_ops, \ - .prcmu_cg_off = _cg_off, \ - .prcmu_cg_bit = _cg_bit, \ - .prcmu_cg_mgt = PRCM_##_reg##_MGT \ +#define DEF_PRCMU_SCALABLE_CLK(_name, _cg_sel) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcmu_scalable_clk_ops, \ + .cg_sel = _cg_sel, \ } -#define DEFINE_PRCMU_CLK_RATE(_name, _cg_off, _cg_bit, _reg, _rate) \ -struct clk clk_##_name = { \ - .name = #_name, \ - .ops = &clk_prcmu_ops, \ - .prcmu_cg_off = _cg_off, \ - .prcmu_cg_bit = _cg_bit, \ - .rate = _rate, \ - .prcmu_cg_mgt = PRCM_##_reg##_MGT \ +/* Use this for clocks that are only defined at OPP 100%. */ +#define DEF_PRCMU_OPP100_CLK(_name, _cg_sel, _rate) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcmu_opp100_clk_ops, \ + .cg_sel = _cg_sel, \ + .rate = _rate, \ + .mutex = &clk_opp100_mutex, \ } -#define DEFINE_PRCC_CLK(_pclust, _name, _bus_en, _kernel_en, _kernclk) \ -struct clk clk_##_name = { \ - .name = #_name, \ - .ops = &clk_prcc_ops, \ - .cluster = _pclust, \ - .prcc_bus = _bus_en, \ - .prcc_kernel = _kernel_en, \ - .parent_cluster = &clk_per##_pclust##clk, \ - .parent_periph = _kernclk \ +/* Define PRCC clock */ +#define DEF_PRCC_PCLK(_name, _io_base, _cg_bit, _parent) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcc_pclk_ops, \ + .io_base = _io_base, \ + .cg_sel = BIT(_cg_bit), \ + .parent = _parent, \ } -#define DEFINE_PRCC_CLK_CUSTOM(_pclust, _name, _bus_en, _kernel_en, _kernclk, _callback, _data) \ -struct clk clk_##_name = { \ - .name = #_name, \ - .ops = &clk_prcc_ops, \ - .cluster = _pclust, \ - .prcc_bus = _bus_en, \ - .prcc_kernel = _kernel_en, \ - .parent_cluster = &clk_per##_pclust##clk, \ - .parent_periph = _kernclk, \ - .get_rate = _callback, \ - .data = (void *) _data \ +#define DEF_PRCC_KCLK(_name, _io_base, _cg_bit, _parent, _clock) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &prcc_kclk_ops, \ + .io_base = _io_base, \ + .cg_sel = BIT(_cg_bit), \ + .parent = _parent, \ + .clock = _clock, \ } +#define DEF_PER_CLK(_name, _bus_parent, _parent) \ + struct clk _name = { \ + .name = #_name, \ + .parent = _parent, \ + .bus_parent = _bus_parent, \ + } -#define CLK(_clk, _devname, _conname) \ - { \ - .clk = &clk_##_clk, \ - .dev_id = _devname, \ - .con_id = _conname, \ +#define DEF_MTU_CLK(_cg_sel, _name, _bus_parent) \ + struct clk _name = { \ + .name = #_name, \ + .ops = &mtu_clk_ops, \ + .cg_sel = _cg_sel, \ + .bus_parent = _bus_parent, \ } -int __init clk_db8500_ed_fixup(void); +/* Functions defined in clock.c */ int __init clk_init(void); +void clks_register(struct clk_lookup *clks, size_t num); +int __clk_enable(struct clk *clk, void *current_lock); +void __clk_disable(struct clk *clk, void *current_lock); +unsigned long __clk_get_rate(struct clk *clk, void *current_lock); +long clk_round_rate_rec(struct clk *clk, unsigned long rate); +int clk_set_rate_rec(struct clk *clk, unsigned long rate); + +#ifdef CONFIG_DEBUG_FS +int dbx500_clk_debug_init(struct clk **clks, int num); +#else +static inline int dbx500_clk_debug_init(struct clk **clks, int num) +{ + return 0; +} +#endif + +#ifdef CONFIG_UX500_SOC_DB8500 +int __init db8500_clk_init(void); +int __init db8500_clk_debug_init(void); +#else +static inline int db8500_clk_init(void) { return 0; } +static inline int db8500_clk_debug_init(void) { return 0;} +#endif + +#ifdef CONFIG_UX500_SOC_DB5500 +int __init db5500_clk_init(void); +int __init db5500_clk_debug_init(void); +#else +static inline int db5500_clk_init(void) { return 0; } +static inline int db5500_clk_debug_init(void) { return 0;} +#endif + +#endif diff --git a/arch/arm/mach-ux500/cpu-db5500.c b/arch/arm/mach-ux500/cpu-db5500.c index 22705d246fc..1a04ca99007 100644 --- a/arch/arm/mach-ux500/cpu-db5500.c +++ b/arch/arm/mach-ux500/cpu-db5500.c @@ -9,20 +9,23 @@ #include <linux/amba/bus.h> #include <linux/io.h> #include <linux/irq.h> +#include <linux/gpio/nomadik.h> -#include <asm/mach/map.h> #include <asm/pmu.h> +#include <asm/mach/map.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> -#include <plat/gpio.h> +#include <linux/gpio.h> #include <mach/hardware.h> #include <mach/devices.h> #include <mach/setup.h> #include <mach/irqs.h> #include <mach/usb.h> +#include <mach/ste-dma40-db5500.h> #include "devices-db5500.h" -#include "ste-dma40-db5500.h" static struct map_desc u5500_uart_io_desc[] __initdata = { __IO_DEV_DESC(U5500_UART0_BASE, SZ_4K), @@ -35,8 +38,11 @@ static struct map_desc u5500_io_desc[] __initdata = { __IO_DEV_DESC(U5500_L2CC_BASE, SZ_4K), __IO_DEV_DESC(U5500_TWD_BASE, SZ_4K), __IO_DEV_DESC(U5500_MTU0_BASE, SZ_4K), + __IO_DEV_DESC(U5500_MTU1_BASE, SZ_4K), __IO_DEV_DESC(U5500_SCU_BASE, SZ_4K), + __IO_DEV_DESC(U5500_RTC_BASE, SZ_4K), __IO_DEV_DESC(U5500_BACKUPRAM0_BASE, SZ_8K), + __MEM_DEV_DESC(U5500_BOOT_ROM_BASE, SZ_1M), __IO_DEV_DESC(U5500_GPIO0_BASE, SZ_4K), __IO_DEV_DESC(U5500_GPIO1_BASE, SZ_4K), @@ -45,26 +51,11 @@ static struct map_desc u5500_io_desc[] __initdata = { __IO_DEV_DESC(U5500_GPIO4_BASE, SZ_4K), __IO_DEV_DESC(U5500_PRCMU_BASE, SZ_4K), __IO_DEV_DESC(U5500_PRCMU_TCDM_BASE, SZ_4K), -}; - -static struct resource db5500_pmu_resources[] = { - [0] = { - .start = IRQ_DB5500_PMU0, - .end = IRQ_DB5500_PMU0, - .flags = IORESOURCE_IRQ, - }, - [1] = { - .start = IRQ_DB5500_PMU1, - .end = IRQ_DB5500_PMU1, - .flags = IORESOURCE_IRQ, - }, -}; - -static struct platform_device db5500_pmu_device = { - .name = "arm-pmu", - .id = ARM_PMU_DEVICE_CPU, - .num_resources = ARRAY_SIZE(db5500_pmu_resources), - .resource = db5500_pmu_resources, + __IO_DEV_DESC(U5500_CLKRST1_BASE, SZ_4K), + __IO_DEV_DESC(U5500_CLKRST2_BASE, SZ_4K), + __IO_DEV_DESC(U5500_CLKRST3_BASE, SZ_4K), + __IO_DEV_DESC(U5500_CLKRST5_BASE, SZ_4K), + __IO_DEV_DESC(U5500_CLKRST6_BASE, SZ_4K), }; static struct resource mbox0_resources[] = { @@ -151,11 +142,15 @@ static struct platform_device mbox2_device = { .num_resources = ARRAY_SIZE(mbox2_resources), }; +static struct platform_device db5500_prcmu_device = { + .name = "db5500-prcmu", +}; + static struct platform_device *db5500_platform_devs[] __initdata = { - &db5500_pmu_device, &mbox0_device, &mbox1_device, &mbox2_device, + &db5500_prcmu_device, }; static resource_size_t __initdata db5500_gpio_base[] = { @@ -179,6 +174,38 @@ static void __init db5500_add_gpios(void) IRQ_DB5500_GPIO0, &pdata); } +static u8 db5500_revision; + +bool cpu_is_u5500v1() +{ + return db5500_revision == 0xA0; +} + +bool cpu_is_u5500v2() +{ + return db5500_revision == 0xB0; +} + +static void db5500_rev_init(void) +{ + const char *version = "UNKNOWN"; + unsigned int asicid; + + /* As in devicemaps_init() */ + local_flush_tlb_all(); + flush_cache_all(); + + asicid = readl_relaxed(__io_address(U5500_ASIC_ID_ADDRESS)); + db5500_revision = asicid & 0xff; + + if (cpu_is_u5500v1()) + version = "1.0"; + else if (cpu_is_u5500v2()) + version = "2.0"; + + pr_info("DB5500 v%s [%#010x]\n", version, asicid); +} + void __init u5500_map_io(void) { /* @@ -191,6 +218,27 @@ void __init u5500_map_io(void) iotable_init(u5500_io_desc, ARRAY_SIZE(u5500_io_desc)); _PRCMU_BASE = __io_address(U5500_PRCMU_BASE); + + db5500_rev_init(); +} + +static void __init db5500_pmu_init(void) +{ + struct resource res[] = { + [0] = { + .start = IRQ_DB5500_PMU0, + .end = IRQ_DB5500_PMU0, + .flags = IORESOURCE_IRQ, + }, + [1] = { + .start = IRQ_DB5500_PMU1, + .end = IRQ_DB5500_PMU1, + .flags = IORESOURCE_IRQ, + }, + }; + + platform_device_register_simple("arm-pmu", ARM_PMU_DEVICE_CPU, + res, ARRAY_SIZE(res)); } static int usb_db5500_rx_dma_cfg[] = { @@ -217,7 +265,14 @@ static int usb_db5500_tx_dma_cfg[] = { void __init u5500_init_devices(void) { + ux500_init_devices(); + +#ifdef CONFIG_STM_TRACE + /* Early init for STM tracing */ + /* platform_device_register(&u5500_stm_device); */ +#endif db5500_add_gpios(); + db5500_pmu_init(); db5500_dma_init(); db5500_add_rtc(); db5500_add_usb(usb_db5500_rx_dma_cfg, usb_db5500_tx_dma_cfg); diff --git a/arch/arm/mach-ux500/cpu-db8500.c b/arch/arm/mach-ux500/cpu-db8500.c index 4598b06c8c5..6a6753e4965 100644 --- a/arch/arm/mach-ux500/cpu-db8500.c +++ b/arch/arm/mach-ux500/cpu-db8500.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2009 ST-Ericsson + * Copyright (C) 2008-2009 ST-Ericsson SA * * Author: Srinidhi KASAGAR <srinidhi.kasagar@stericsson.com> * @@ -14,19 +14,22 @@ #include <linux/amba/bus.h> #include <linux/interrupt.h> #include <linux/irq.h> -#include <linux/gpio.h> +#include <linux/gpio/nomadik.h> #include <linux/platform_device.h> #include <linux/io.h> +#include <linux/sys_soc.h> #include <asm/mach/map.h> #include <asm/pmu.h> #include <mach/hardware.h> #include <mach/setup.h> #include <mach/devices.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <mach/reboot_reasons.h> #include <mach/usb.h> +#include <mach/ste-dma40-db8500.h> #include "devices-db8500.h" -#include "ste-dma40-db8500.h" /* minimum static i/o mapping required to boot U8500 platforms */ static struct map_desc u8500_uart_io_desc[] __initdata = { @@ -40,8 +43,11 @@ static struct map_desc u8500_io_desc[] __initdata = { __IO_DEV_DESC(U8500_L2CC_BASE, SZ_4K), __IO_DEV_DESC(U8500_TWD_BASE, SZ_4K), __IO_DEV_DESC(U8500_MTU0_BASE, SZ_4K), + __IO_DEV_DESC(U8500_MTU1_BASE, SZ_4K), + __IO_DEV_DESC(U8500_RTC_BASE, SZ_4K), __IO_DEV_DESC(U8500_SCU_BASE, SZ_4K), __IO_DEV_DESC(U8500_BACKUPRAM0_BASE, SZ_8K), + __MEM_DEV_DESC(U8500_BOOT_ROM_BASE, SZ_1M), __IO_DEV_DESC(U8500_CLKRST1_BASE, SZ_4K), __IO_DEV_DESC(U8500_CLKRST2_BASE, SZ_4K), @@ -54,19 +60,6 @@ static struct map_desc u8500_io_desc[] __initdata = { __IO_DEV_DESC(U8500_GPIO1_BASE, SZ_4K), __IO_DEV_DESC(U8500_GPIO2_BASE, SZ_4K), __IO_DEV_DESC(U8500_GPIO3_BASE, SZ_4K), -}; - -static struct map_desc u8500_ed_io_desc[] __initdata = { - __IO_DEV_DESC(U8500_MTU0_BASE_ED, SZ_4K), - __IO_DEV_DESC(U8500_CLKRST7_BASE_ED, SZ_8K), -}; - -static struct map_desc u8500_v1_io_desc[] __initdata = { - __IO_DEV_DESC(U8500_MTU0_BASE, SZ_4K), - __IO_DEV_DESC(U8500_PRCMU_TCDM_BASE_V1, SZ_4K), -}; - -static struct map_desc u8500_v2_io_desc[] __initdata = { __IO_DEV_DESC(U8500_PRCMU_TCDM_BASE, SZ_4K), }; @@ -81,13 +74,6 @@ void __init u8500_map_io(void) iotable_init(u8500_io_desc, ARRAY_SIZE(u8500_io_desc)); - if (cpu_is_u8500ed()) - iotable_init(u8500_ed_io_desc, ARRAY_SIZE(u8500_ed_io_desc)); - else if (cpu_is_u8500v1()) - iotable_init(u8500_v1_io_desc, ARRAY_SIZE(u8500_v1_io_desc)); - else if (cpu_is_u8500v2()) - iotable_init(u8500_v2_io_desc, ARRAY_SIZE(u8500_v2_io_desc)); - _PRCMU_BASE = __io_address(U8500_PRCMU_BASE); } @@ -136,36 +122,20 @@ static struct platform_device db8500_prcmu_device = { }; static struct platform_device *platform_devs[] __initdata = { - &u8500_dma40_device, + &u8500_gpio_devs[0], + &u8500_gpio_devs[1], + &u8500_gpio_devs[2], + &u8500_gpio_devs[3], + &u8500_gpio_devs[4], + &u8500_gpio_devs[5], + &u8500_gpio_devs[6], + &u8500_gpio_devs[7], + &u8500_gpio_devs[8], &db8500_pmu_device, &db8500_prcmu_device, + &u8500_wdt_device, }; -static resource_size_t __initdata db8500_gpio_base[] = { - U8500_GPIOBANK0_BASE, - U8500_GPIOBANK1_BASE, - U8500_GPIOBANK2_BASE, - U8500_GPIOBANK3_BASE, - U8500_GPIOBANK4_BASE, - U8500_GPIOBANK5_BASE, - U8500_GPIOBANK6_BASE, - U8500_GPIOBANK7_BASE, - U8500_GPIOBANK8_BASE, -}; - -static void __init db8500_add_gpios(void) -{ - struct nmk_gpio_platform_data pdata = { - /* No custom data yet */ - }; - - if (cpu_is_u8500v2()) - pdata.supports_sleepmode = true; - - dbx500_add_gpios(ARRAY_AND_SIZE(db8500_gpio_base), - IRQ_DB8500_GPIO0, &pdata); -} - static int usb_db8500_rx_dma_cfg[] = { DB8500_DMA_DEV38_USB_OTG_IEP_1_9, DB8500_DMA_DEV37_USB_OTG_IEP_2_10, @@ -193,11 +163,15 @@ static int usb_db8500_tx_dma_cfg[] = { */ void __init u8500_init_devices(void) { - if (cpu_is_u8500ed()) - dma40_u8500ed_fixup(); + ux500_init_devices(); +#ifdef CONFIG_STM_TRACE + /* Early init for STM tracing */ + platform_device_register(&u8500_stm_device); +#endif + + db8500_dma_init(); db8500_add_rtc(); - db8500_add_gpios(); db8500_add_usb(usb_db8500_rx_dma_cfg, usb_db8500_tx_dma_cfg); platform_device_register_simple("cpufreq-u8500", -1, NULL, 0); @@ -205,3 +179,82 @@ void __init u8500_init_devices(void) return ; } + +#ifdef CONFIG_SYS_SOC +#define U8500_BB_UID_BASE (U8500_BACKUPRAM1_BASE + 0xFC0) +#define U8500_BB_UID_LENGTH 5 + +static ssize_t ux500_get_machine(char *buf, struct sysfs_soc_info *si) +{ + return sprintf(buf, "DB%2x00\n", dbx500_id.partnumber); +} + +static ssize_t ux500_get_soc_id(char *buf, struct sysfs_soc_info *si) +{ + void __iomem *uid_base; + int i; + ssize_t sz = 0; + + if (dbx500_id.partnumber == 0x85) { + uid_base = __io_address(U8500_BB_UID_BASE); + for (i = 0; i < U8500_BB_UID_LENGTH; i++) + sz += sprintf(buf + sz, "%08x", readl(uid_base + i * sizeof(u32))); + sz += sprintf(buf + sz, "\n"); + } + else { + /* Don't know where it is located for U5500 */ + sz = sprintf(buf, "N/A\n"); + } + + return sz; +} + +static ssize_t ux500_get_revision(char *buf, struct sysfs_soc_info *si) +{ + unsigned int rev = dbx500_id.revision; + + if (rev == 0x01) + return sprintf(buf, "%s\n", "ED"); + else if (rev >= 0xA0) + return sprintf(buf, "%d.%d\n" , (rev >> 4) - 0xA + 1, rev & 0xf); + + return sprintf(buf, "%s", "Unknown\n"); +} + +static ssize_t ux500_get_process(char *buf, struct sysfs_soc_info *si) +{ + if (dbx500_id.process == 0x00) + return sprintf(buf, "Standard\n"); + + return sprintf(buf, "%02xnm\n", dbx500_id.process); +} + +static ssize_t ux500_get_reset_code(char *buf, struct sysfs_soc_info *si) +{ + return sprintf(buf, "0x%04x\n", prcmu_get_reset_code()); +} + +static ssize_t ux500_get_reset_reason(char *buf, struct sysfs_soc_info *si) +{ + return sprintf(buf, "%s\n", + reboot_reason_string(prcmu_get_reset_code())); +} + +static struct sysfs_soc_info soc_info[] = { + SYSFS_SOC_ATTR_CALLBACK("machine", ux500_get_machine), + SYSFS_SOC_ATTR_VALUE("family", "Ux500"), + SYSFS_SOC_ATTR_CALLBACK("soc_id", ux500_get_soc_id), + SYSFS_SOC_ATTR_CALLBACK("revision", ux500_get_revision), + SYSFS_SOC_ATTR_CALLBACK("process", ux500_get_process), + SYSFS_SOC_ATTR_CALLBACK("reset_code", ux500_get_reset_code), + SYSFS_SOC_ATTR_CALLBACK("reset_reason", ux500_get_reset_reason), +}; + +static int __init ux500_sys_soc_init(void) +{ + return register_sysfs_soc(soc_info, ARRAY_SIZE(soc_info)); +} + +module_init(ux500_sys_soc_init); +#endif + diff --git a/arch/arm/mach-ux500/cpu.c b/arch/arm/mach-ux500/cpu.c index 1da23bb87c1..7274ab2f90f 100644 --- a/arch/arm/mach-ux500/cpu.c +++ b/arch/arm/mach-ux500/cpu.c @@ -8,8 +8,9 @@ #include <linux/platform_device.h> #include <linux/io.h> #include <linux/clk.h> -#include <linux/mfd/db8500-prcmu.h> -#include <linux/mfd/db5500-prcmu.h> +#include <linux/delay.h> +#include <linux/clksrc-dbx500-prcmu.h> +#include <linux/mfd/dbx500-prcmu.h> #include <asm/cacheflush.h> #include <asm/hardware/cache-l2x0.h> @@ -17,10 +18,10 @@ #include <asm/mach/map.h> #include <asm/localtimer.h> -#include <plat/mtu.h> #include <mach/hardware.h> #include <mach/setup.h> #include <mach/devices.h> +#include <mach/reboot_reasons.h> #include "clock.h" @@ -30,6 +31,36 @@ void __iomem *_PRCMU_BASE; static void __iomem *l2x0_base; #endif +void __init ux500_init_devices(void) +{ +#ifdef CONFIG_CACHE_L2X0 + BUG_ON(!l2x0_base); + + /* + * Unlock Data and Instruction Lock if locked. This is done here + * instead of in l2x0_init since doing it there appears to cause the + * second core boot to occasionaly fail. + */ + if (readl_relaxed(l2x0_base + L2X0_LOCKDOWN_WAY_D_BASE) & 0xFF) + writel_relaxed(0x0, l2x0_base + L2X0_LOCKDOWN_WAY_D_BASE); + + if (readl_relaxed(l2x0_base + L2X0_LOCKDOWN_WAY_I_BASE) & 0xFF) + writel_relaxed(0x0, l2x0_base + L2X0_LOCKDOWN_WAY_I_BASE); +#endif +} + +static void ux500_restart(char mode, const char *cmd) +{ + unsigned short reset_code; + + reset_code = reboot_reason_code(cmd); + prcmu_system_reset(reset_code); + + mdelay(1000); + printk("Reboot via PRCMU failed -- System halted\n"); + while (1); +} + void __init ux500_init_irq(void) { void __iomem *dist_base; @@ -50,10 +81,8 @@ void __init ux500_init_irq(void) * Init clocks here so that they are available for system timer * initialization. */ - if (cpu_is_u5500()) - db5500_prcmu_early_init(); - if (cpu_is_u8500()) - prcmu_early_init(); + prcmu_early_init(); + arm_pm_restart = ux500_restart; clk_init(); } @@ -119,30 +148,3 @@ static int ux500_l2x0_init(void) } early_initcall(ux500_l2x0_init); #endif - -static void __init ux500_timer_init(void) -{ -#ifdef CONFIG_LOCAL_TIMERS - /* Setup the local timer base */ - if (cpu_is_u5500()) - twd_base = __io_address(U5500_TWD_BASE); - else if (cpu_is_u8500()) - twd_base = __io_address(U8500_TWD_BASE); - else - ux500_unknown_soc(); -#endif - if (cpu_is_u5500()) - mtu_base = __io_address(U5500_MTU0_BASE); - else if (cpu_is_u8500ed()) - mtu_base = __io_address(U8500_MTU0_BASE_ED); - else if (cpu_is_u8500()) - mtu_base = __io_address(U8500_MTU0_BASE); - else - ux500_unknown_soc(); - - nmdk_timer_init(); -} - -struct sys_timer ux500_timer = { - .init = ux500_timer_init, -}; diff --git a/arch/arm/mach-ux500/devices-common.c b/arch/arm/mach-ux500/devices-common.c index 13a4ce046ae..1de666a8104 100644 --- a/arch/arm/mach-ux500/devices-common.c +++ b/arch/arm/mach-ux500/devices-common.c @@ -12,10 +12,18 @@ #include <linux/slab.h> #include <linux/platform_device.h> #include <linux/amba/bus.h> +#include <linux/pm.h> +#include <linux/gpio.h> +#include <linux/gpio/nomadik.h> -#include <plat/gpio.h> +#ifdef CONFIG_FB_MCDE +#include <video/mcde_display.h> +#include <video/mcde_display-av8100.h> +#include <video/mcde_fb.h> +#endif #include <mach/hardware.h> +#include <mach/pm.h> #include "devices-common.h" @@ -38,6 +46,7 @@ dbx500_add_amba_device(const char *name, resource_size_t base, dev->dma_mask = DMA_BIT_MASK(32); dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + dev->dev.pm_domain = &ux500_amba_dev_power_domain; dev->irq[0] = irq; dev->irq[1] = NO_IRQ; @@ -68,6 +77,7 @@ dbx500_add_platform_device(const char *name, int id, void *pdata, dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); dev->dev.dma_mask = &dev->dev.coherent_dma_mask; + dev->dev.pm_domain = &ux500_dev_power_domain; ret = platform_device_add_resources(dev, res, resnum); if (ret) @@ -108,6 +118,22 @@ dbx500_add_platform_device_4k1irq(const char *name, int id, ARRAY_SIZE(resources)); } +struct platform_device * +dbx500_add_platform_device_noirq(const char *name, int id, + resource_size_t base, void *pdata) +{ + struct resource resources[] = { + [0] = { + .start = base, + .end = base + SZ_4K - 1, + .flags = IORESOURCE_MEM, + } + }; + + return dbx500_add_platform_device(name, id, pdata, resources, + ARRAY_SIZE(resources)); +} + static struct platform_device * dbx500_add_gpio(int id, resource_size_t addr, int irq, struct nmk_gpio_platform_data *pdata) @@ -140,7 +166,70 @@ void dbx500_add_gpios(resource_size_t *base, int num, int irq, pdata->first_gpio = first; pdata->first_irq = NOMADIK_GPIO_TO_IRQ(first); pdata->num_gpio = 32; - dbx500_add_gpio(i, base[i], irq, pdata); } } + +#ifdef CONFIG_FB_MCDE +void hdmi_fb_onoff(struct mcde_display_device *ddev, + bool enable, u8 cea, u8 vesa_cea_nr) +{ + struct fb_info *fbi; + u16 w, h; + u16 vw, vh; + u32 rotate = FB_ROTATE_UR; + struct display_driver_data *driver_data = dev_get_drvdata(&ddev->dev); + + dev_dbg(&ddev->dev, "%s\n", __func__); + dev_dbg(&ddev->dev, "en:%d cea:%d nr:%d\n", enable, cea, vesa_cea_nr); + + if (enable) { + if (ddev->enabled) { + dev_dbg(&ddev->dev, "Display is already enabled.\n"); + return; + } + + /* Create fb */ + if (ddev->fbi == NULL) { + /* Note: change when dynamic buffering is available */ + int buffering = 2; + + /* Get default values */ + mcde_dss_get_native_resolution(ddev, &w, &h); + vw = w; + vh = h * buffering; + + if (vesa_cea_nr != 0) + ddev->ceanr_convert(ddev, cea, vesa_cea_nr, + buffering, &w, &h, &vw, &vh); + + fbi = mcde_fb_create(ddev, w, h, vw, vh, + ddev->default_pixel_format, rotate); + + if (IS_ERR(fbi)) { + dev_warn(&ddev->dev, + "Failed to create fb for display %s\n", + ddev->name); + goto hdmi_fb_onoff_end; + } else { + dev_info(&ddev->dev, + "Framebuffer created (%s)\n", + ddev->name); + } + driver_data->fbdevname = (char *)dev_name(fbi->dev); + } + } else { + if (!ddev->enabled) { + dev_dbg(&ddev->dev, "Display %s is already disabled.\n", + ddev->name); + return; + } + mcde_fb_destroy(ddev); + } + +hdmi_fb_onoff_end: + return; +} +EXPORT_SYMBOL(hdmi_fb_onoff); +#endif + diff --git a/arch/arm/mach-ux500/devices-common.h b/arch/arm/mach-ux500/devices-common.h index 7825705033b..bbf551f6318 100644 --- a/arch/arm/mach-ux500/devices-common.h +++ b/arch/arm/mach-ux500/devices-common.h @@ -8,6 +8,8 @@ #ifndef __DEVICES_COMMON_H #define __DEVICES_COMMON_H +#include <linux/amba/serial.h> + extern struct amba_device * dbx500_add_amba_device(const char *name, resource_size_t base, int irq, void *pdata, unsigned int periphid); @@ -17,18 +19,24 @@ dbx500_add_platform_device_4k1irq(const char *name, int id, resource_size_t base, int irq, void *pdata); -struct spi_master_cntlr; +extern struct platform_device * +dbx500_add_platform_device_noirq(const char *name, int id, + resource_size_t base, void *pdata); + +struct stm_msp_controller; static inline struct amba_device * dbx500_add_msp_spi(const char *name, resource_size_t base, int irq, - struct spi_master_cntlr *pdata) + struct stm_msp_controller *pdata) { return dbx500_add_amba_device(name, base, irq, pdata, 0); } +struct pl022_ssp_controller; + static inline struct amba_device * dbx500_add_spi(const char *name, resource_size_t base, int irq, - struct spi_master_cntlr *pdata, + struct pl022_ssp_controller *pdata, u32 periphid) { return dbx500_add_amba_device(name, base, irq, pdata, periphid); @@ -79,6 +87,25 @@ dbx500_add_rtc(resource_size_t base, int irq) return dbx500_add_amba_device("rtc-pl031", base, irq, NULL, 0); } +struct cryp_platform_data; + +static inline struct platform_device * +dbx500_add_cryp1(int id, resource_size_t base, int irq, + struct cryp_platform_data *pdata) +{ + return dbx500_add_platform_device_4k1irq("cryp1", id, base, irq, + pdata); +} + +struct hash_platform_data; + +static inline struct platform_device * +dbx500_add_hash1(int id, resource_size_t base, + struct hash_platform_data *pdata) +{ + return dbx500_add_platform_device_noirq("hash1", id, base, pdata); +} + struct nmk_gpio_platform_data; void dbx500_add_gpios(resource_size_t *base, int num, int irq, diff --git a/arch/arm/mach-ux500/devices-db5500.c b/arch/arm/mach-ux500/devices-db5500.c new file mode 100644 index 00000000000..299e3f06479 --- /dev/null +++ b/arch/arm/mach-ux500/devices-db5500.c @@ -0,0 +1,280 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + * + * Author: Pierre Peiffer <pierre.peiffer@stericsson.com> for ST-Ericsson. + * for the System Trace Module part. + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/gpio/nomadik.h> + +#include <mach/hardware.h> +#include <mach/devices.h> + +#ifdef CONFIG_FB_MCDE +#include <video/mcde.h> +#endif +#include <mach/db5500-regs.h> + +#include <linux/mfd/dbx500-prcmu.h> +#include <mach/pm.h> + +#define GPIO_DATA(_name, first, num) \ + { \ + .name = _name, \ + .first_gpio = first, \ + .first_irq = NOMADIK_GPIO_TO_IRQ(first), \ + .num_gpio = num, \ + .get_secondary_status = ux500_pm_gpio_read_wake_up_status, \ + .set_ioforce = ux500_pm_prcmu_set_ioforce, \ + } + +#define GPIO_RESOURCE(block) \ + { \ + .start = U5500_GPIOBANK##block##_BASE, \ + .end = U5500_GPIOBANK##block##_BASE + 127, \ + .flags = IORESOURCE_MEM, \ + }, \ + { \ + .start = IRQ_DB5500_GPIO##block, \ + .end = IRQ_DB5500_GPIO##block, \ + .flags = IORESOURCE_IRQ, \ + }, \ + { \ + .start = IRQ_DB5500_PRCMU_GPIO##block, \ + .end = IRQ_DB5500_PRCMU_GPIO##block, \ + .flags = IORESOURCE_IRQ, \ + } + +#define GPIO_DEVICE(block) \ + { \ + .name = "gpio", \ + .id = block, \ + .num_resources = 3, \ + .resource = &u5500_gpio_resources[block * 3], \ + .dev = { \ + .platform_data = &u5500_gpio_data[block], \ + }, \ + } + +static struct nmk_gpio_platform_data u5500_gpio_data[] = { + GPIO_DATA("GPIO-0-31", 0, 32), + GPIO_DATA("GPIO-32-63", 32, 4), /* 36..63 not routed to pin */ + GPIO_DATA("GPIO-64-95", 64, 19), /* 83..95 not routed to pin */ + GPIO_DATA("GPIO-96-127", 96, 6), /* 102..127 not routed to pin */ + GPIO_DATA("GPIO-128-159", 128, 21), /* 149..159 not routed to pin */ + GPIO_DATA("GPIO-160-191", 160, 32), + GPIO_DATA("GPIO-192-223", 192, 32), + GPIO_DATA("GPIO-224-255", 224, 4), /* 228..255 not routed to pin */ +}; + +static struct resource u5500_gpio_resources[] = { + GPIO_RESOURCE(0), + GPIO_RESOURCE(1), + GPIO_RESOURCE(2), + GPIO_RESOURCE(3), + GPIO_RESOURCE(4), + GPIO_RESOURCE(5), + GPIO_RESOURCE(6), + GPIO_RESOURCE(7), +}; + +struct platform_device u5500_gpio_devs[] = { + GPIO_DEVICE(0), + GPIO_DEVICE(1), + GPIO_DEVICE(2), + GPIO_DEVICE(3), + GPIO_DEVICE(4), + GPIO_DEVICE(5), + GPIO_DEVICE(6), + GPIO_DEVICE(7), +}; + +#define U5500_PWM_SIZE 0x20 +static struct resource u5500_pwm0_resource[] = { + { + .name = "PWM_BASE", + .start = U5500_PWM_BASE, + .end = U5500_PWM_BASE + U5500_PWM_SIZE - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource u5500_pwm1_resource[] = { + { + .name = "PWM_BASE", + .start = U5500_PWM_BASE + U5500_PWM_SIZE, + .end = U5500_PWM_BASE + U5500_PWM_SIZE * 2 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource u5500_pwm2_resource[] = { + { + .name = "PWM_BASE", + .start = U5500_PWM_BASE + U5500_PWM_SIZE * 2, + .end = U5500_PWM_BASE + U5500_PWM_SIZE * 3 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct resource u5500_pwm3_resource[] = { + { + .name = "PWM_BASE", + .start = U5500_PWM_BASE + U5500_PWM_SIZE * 3, + .end = U5500_PWM_BASE + U5500_PWM_SIZE * 4 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +struct platform_device u5500_pwm0_device = { + .id = 0, + .name = "pwm", + .resource = u5500_pwm0_resource, + .num_resources = ARRAY_SIZE(u5500_pwm0_resource), +}; + +struct platform_device u5500_pwm1_device = { + .id = 1, + .name = "pwm", + .resource = u5500_pwm1_resource, + .num_resources = ARRAY_SIZE(u5500_pwm1_resource), +}; + +struct platform_device u5500_pwm2_device = { + .id = 2, + .name = "pwm", + .resource = u5500_pwm2_resource, + .num_resources = ARRAY_SIZE(u5500_pwm2_resource), +}; + +struct platform_device u5500_pwm3_device = { + .id = 3, + .name = "pwm", + .resource = u5500_pwm3_resource, + .num_resources = ARRAY_SIZE(u5500_pwm3_resource), +}; + +#ifdef CONFIG_FB_MCDE +static struct resource mcde_resources[] = { + [0] = { + .name = MCDE_IO_AREA, + .start = U5500_MCDE_BASE, + .end = U5500_MCDE_BASE + U5500_MCDE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = MCDE_IO_AREA, + .start = U5500_DSI_LINK1_BASE, + .end = U5500_DSI_LINK1_BASE + U5500_DSI_LINK_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [2] = { + .name = MCDE_IO_AREA, + .start = U5500_DSI_LINK2_BASE, + .end = U5500_DSI_LINK2_BASE + U5500_DSI_LINK_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [3] = { + .name = MCDE_IRQ, + .start = IRQ_DB5500_DISP, + .end = IRQ_DB5500_DISP, + .flags = IORESOURCE_IRQ, + }, +}; + +static int mcde_platform_enable_dsipll(void) +{ + return prcmu_enable_dsipll(); +} + +static int mcde_platform_disable_dsipll(void) +{ + return prcmu_disable_dsipll(); +} + +static int mcde_platform_set_display_clocks(void) +{ + return prcmu_set_display_clocks(); +} + +static struct mcde_platform_data mcde_pdata = { + .num_dsilinks = 2, + .syncmux = 0x01, + .num_channels = 2, + .num_overlays = 3, + .regulator_mcde_epod_id = "vsupply", + .regulator_esram_epod_id = "v-esram12", +#ifdef CONFIG_MCDE_DISPLAY_DSI + .clock_dsi_id = "hdmi", + .clock_dsi_lp_id = "tv", +#endif + .clock_mcde_id = "mcde", + .platform_set_clocks = mcde_platform_set_display_clocks, + .platform_enable_dsipll = mcde_platform_enable_dsipll, + .platform_disable_dsipll = mcde_platform_disable_dsipll, +}; + +struct platform_device u5500_mcde_device = { + .name = "mcde", + .id = -1, + .dev = { + .platform_data = &mcde_pdata, + }, + .num_resources = ARRAY_SIZE(mcde_resources), + .resource = mcde_resources, +}; +#endif + +static struct resource b2r2_resources[] = { + [0] = { + .start = U5500_B2R2_BASE, + .end = U5500_B2R2_BASE + ((4*1024)-1), + .name = "b2r2_base", + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "B2R2_IRQ", + .start = IRQ_DB5500_B2R2, + .end = IRQ_DB5500_B2R2, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device u5500_b2r2_device = { + .name = "b2r2", + .id = 0, + .dev = { + .init_name = "b2r2_bus", + .coherent_dma_mask = ~0, + }, + .num_resources = ARRAY_SIZE(b2r2_resources), + .resource = b2r2_resources, +}; + +static struct resource u5500_thsens_resources[] = { + [0] = { + .name = "IRQ_HOTMON_LOW", + .start = IRQ_DB5500_PRCMU_TEMP_SENSOR_LOW, + .end = IRQ_DB5500_PRCMU_TEMP_SENSOR_LOW, + .flags = IORESOURCE_IRQ, + }, + [1] = { + .name = "IRQ_HOTMON_HIGH", + .start = IRQ_DB5500_PRCMU_TEMP_SENSOR_HIGH, + .end = IRQ_DB5500_PRCMU_TEMP_SENSOR_HIGH, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device u5500_thsens_device = { + .name = "db5500_temp", + .resource = u5500_thsens_resources, + .num_resources = ARRAY_SIZE(u5500_thsens_resources), +}; diff --git a/arch/arm/mach-ux500/devices-db5500.h b/arch/arm/mach-ux500/devices-db5500.h index 0c4bccd02b9..a6536edd2d1 100644 --- a/arch/arm/mach-ux500/devices-db5500.h +++ b/arch/arm/mach-ux500/devices-db5500.h @@ -17,6 +17,16 @@ #define db5500_add_i2c3(pdata) \ dbx500_add_i2c(3, U5500_I2C3_BASE, IRQ_DB5500_I2C3, pdata) +struct db5500_keypad_platform_data; + +static inline struct platform_device * +db5500_add_keypad(struct db5500_keypad_platform_data *pdata) +{ + return dbx500_add_platform_device_4k1irq("db5500-keypad", -1, + U5500_KEYPAD_BASE, + IRQ_DB5500_KBD, pdata); +} + #define db5500_add_msp0_i2s(pdata) \ dbx500_add_msp_i2s(0, U5500_MSP0_BASE, IRQ_DB5500_MSP0, pdata) #define db5500_add_msp1_i2s(pdata) \ @@ -37,21 +47,21 @@ #define db5500_add_usb(rx_cfg, tx_cfg) \ ux500_add_usb(U5500_USBOTG_BASE, IRQ_DB5500_USBOTG, rx_cfg, tx_cfg) -#define db5500_add_sdi0(pdata) \ +#define db5500_add_sdi0(pdata, pid) \ dbx500_add_sdi("sdi0", U5500_SDI0_BASE, IRQ_DB5500_SDMMC0, pdata, \ - 0x10480180) -#define db5500_add_sdi1(pdata) \ + pid) +#define db5500_add_sdi1(pdata, pid) \ dbx500_add_sdi("sdi1", U5500_SDI1_BASE, IRQ_DB5500_SDMMC1, pdata, \ - 0x10480180) -#define db5500_add_sdi2(pdata) \ - dbx500_add_sdi("sdi2", U5500_SDI2_BASE, IRQ_DB5500_SDMMC2, pdata \ - 0x10480180) -#define db5500_add_sdi3(pdata) \ - dbx500_add_sdi("sdi3", U5500_SDI3_BASE, IRQ_DB5500_SDMMC3, pdata \ - 0x10480180) -#define db5500_add_sdi4(pdata) \ - dbx500_add_sdi("sdi4", U5500_SDI4_BASE, IRQ_DB5500_SDMMC4, pdata \ - 0x10480180) + pid) +#define db5500_add_sdi2(pdata, pid) \ + dbx500_add_sdi("sdi2", U5500_SDI2_BASE, IRQ_DB5500_SDMMC2, pdata, \ + pid) +#define db5500_add_sdi3(pdata, pid) \ + dbx500_add_sdi("sdi3", U5500_SDI3_BASE, IRQ_DB5500_SDMMC3, pdata, \ + pid) +#define db5500_add_sdi4(pdata, pid) \ + dbx500_add_sdi("sdi4", U5500_SDI4_BASE, IRQ_DB5500_SDMMC4, pdata, \ + pid) /* This one has a bad peripheral ID in the U5500 silicon */ #define db5500_add_spi0(pdata) \ @@ -61,10 +71,10 @@ dbx500_add_spi("spi1", U5500_SPI1_BASE, IRQ_DB5500_SPI1, pdata, \ 0x10080023) #define db5500_add_spi2(pdata) \ - dbx500_add_spi("spi2", U5500_SPI2_BASE, IRQ_DB5500_SPI2, pdata \ + dbx500_add_spi("spi2", U5500_SPI2_BASE, IRQ_DB5500_SPI2, pdata, \ 0x10080023) #define db5500_add_spi3(pdata) \ - dbx500_add_spi("spi3", U5500_SPI3_BASE, IRQ_DB5500_SPI3, pdata \ + dbx500_add_spi("spi3", U5500_SPI3_BASE, IRQ_DB5500_SPI3, pdata, \ 0x10080023) #define db5500_add_uart0(plat) \ @@ -76,4 +86,9 @@ #define db5500_add_uart3(plat) \ dbx500_add_uart("uart3", U5500_UART3_BASE, IRQ_DB5500_UART3, plat) +#define db5500_add_cryp1(pdata) \ + dbx500_add_cryp1(-1, U5500_CRYP1_BASE, IRQ_DB5500_CRYP1, pdata) +#define db5500_add_hash1(pdata) \ + dbx500_add_hash1(-1, U5500_HASH1_BASE, pdata) + #endif diff --git a/arch/arm/mach-ux500/devices-db8500.c b/arch/arm/mach-ux500/devices-db8500.c index 73b17404b19..2d74f4acddb 100644 --- a/arch/arm/mach-ux500/devices-db8500.c +++ b/arch/arm/mach-ux500/devices-db8500.c @@ -10,172 +10,444 @@ #include <linux/interrupt.h> #include <linux/io.h> #include <linux/gpio.h> +#include <linux/gpio/nomadik.h> #include <linux/amba/bus.h> #include <linux/amba/pl022.h> +#include <plat/pincfg.h> #include <plat/ste_dma40.h> +#include <mach/devices.h> #include <mach/hardware.h> #include <mach/setup.h> +#include <mach/pm.h> +#ifdef CONFIG_FB_MCDE +#include <video/mcde.h> +#endif +#include <linux/mfd/dbx500-prcmu.h> +#ifdef CONFIG_HSI +#include <mach/hsi.h> +#endif +#include <mach/ste-dma40-db8500.h> -#include "ste-dma40-db8500.h" +#include "pins-db8500.h" -static struct resource dma40_resources[] = { +#define GPIO_DATA(_name, first, num) \ + { \ + .name = _name, \ + .first_gpio = first, \ + .first_irq = NOMADIK_GPIO_TO_IRQ(first), \ + .num_gpio = num, \ + .get_secondary_status = ux500_pm_gpio_read_wake_up_status, \ + .set_ioforce = ux500_pm_prcmu_set_ioforce, \ + .supports_sleepmode = true, \ + } + +#define GPIO_RESOURCE(block) \ + { \ + .start = U8500_GPIOBANK##block##_BASE, \ + .end = U8500_GPIOBANK##block##_BASE + 127, \ + .flags = IORESOURCE_MEM, \ + }, \ + { \ + .start = IRQ_DB8500_GPIO##block, \ + .end = IRQ_DB8500_GPIO##block, \ + .flags = IORESOURCE_IRQ, \ + }, \ + { \ + .start = IRQ_PRCMU_GPIO##block, \ + .end = IRQ_PRCMU_GPIO##block, \ + .flags = IORESOURCE_IRQ, \ + } + +#define GPIO_DEVICE(block) \ + { \ + .name = "gpio", \ + .id = block, \ + .num_resources = 3, \ + .resource = &u8500_gpio_resources[block * 3], \ + .dev = { \ + .platform_data = &u8500_gpio_data[block], \ + }, \ + } + +static struct nmk_gpio_platform_data u8500_gpio_data[] = { + GPIO_DATA("GPIO-0-31", 0, 32), + GPIO_DATA("GPIO-32-63", 32, 5), /* 37..63 not routed to pin */ + GPIO_DATA("GPIO-64-95", 64, 32), + GPIO_DATA("GPIO-96-127", 96, 2), /* 98..127 not routed to pin */ + GPIO_DATA("GPIO-128-159", 128, 32), + GPIO_DATA("GPIO-160-191", 160, 12), /* 172..191 not routed to pin */ + GPIO_DATA("GPIO-192-223", 192, 32), + GPIO_DATA("GPIO-224-255", 224, 7), /* 231..255 not routed to pin */ + GPIO_DATA("GPIO-256-288", 256, 12), /* 268..288 not routed to pin */ +}; + +static struct resource u8500_gpio_resources[] = { + GPIO_RESOURCE(0), + GPIO_RESOURCE(1), + GPIO_RESOURCE(2), + GPIO_RESOURCE(3), + GPIO_RESOURCE(4), + GPIO_RESOURCE(5), + GPIO_RESOURCE(6), + GPIO_RESOURCE(7), + GPIO_RESOURCE(8), +}; + +struct platform_device u8500_gpio_devs[] = { + GPIO_DEVICE(0), + GPIO_DEVICE(1), + GPIO_DEVICE(2), + GPIO_DEVICE(3), + GPIO_DEVICE(4), + GPIO_DEVICE(5), + GPIO_DEVICE(6), + GPIO_DEVICE(7), + GPIO_DEVICE(8), +}; + +static struct resource u8500_shrm_resources[] = { [0] = { - .start = U8500_DMA_BASE, - .end = U8500_DMA_BASE + SZ_4K - 1, + .start = U8500_SHRM_GOP_INTERRUPT_BASE, + .end = U8500_SHRM_GOP_INTERRUPT_BASE + ((4*4)-1), + .name = "shrm_gop_register_base", .flags = IORESOURCE_MEM, - .name = "base", }, [1] = { - .start = U8500_DMA_LCPA_BASE, - .end = U8500_DMA_LCPA_BASE + 2 * SZ_1K - 1, - .flags = IORESOURCE_MEM, - .name = "lcpa", + .start = IRQ_CA_WAKE_REQ_V1, + .end = IRQ_CA_WAKE_REQ_V1, + .name = "ca_irq_wake_req", + .flags = IORESOURCE_IRQ, }, [2] = { - .start = IRQ_DB8500_DMA, - .end = IRQ_DB8500_DMA, + .start = IRQ_AC_READ_NOTIFICATION_0_V1, + .end = IRQ_AC_READ_NOTIFICATION_0_V1, + .name = "ac_read_notification_0_irq", + .flags = IORESOURCE_IRQ, + }, + [3] = { + .start = IRQ_AC_READ_NOTIFICATION_1_V1, + .end = IRQ_AC_READ_NOTIFICATION_1_V1, + .name = "ac_read_notification_1_irq", + .flags = IORESOURCE_IRQ, + }, + [4] = { + .start = IRQ_CA_MSG_PEND_NOTIFICATION_0_V1, + .end = IRQ_CA_MSG_PEND_NOTIFICATION_0_V1, + .name = "ca_msg_pending_notification_0_irq", + .flags = IORESOURCE_IRQ, + }, + [5] = { + .start = IRQ_CA_MSG_PEND_NOTIFICATION_1_V1, + .end = IRQ_CA_MSG_PEND_NOTIFICATION_1_V1, + .name = "ca_msg_pending_notification_1_irq", .flags = IORESOURCE_IRQ, } }; -/* Default configuration for physcial memcpy */ -struct stedma40_chan_cfg dma40_memcpy_conf_phy = { - .mode = STEDMA40_MODE_PHYSICAL, - .dir = STEDMA40_MEM_TO_MEM, +struct platform_device u8500_shrm_device = { + .name = "u8500_shrm", + .id = 0, + .dev = { + .init_name = "shrm_bus", + .coherent_dma_mask = ~0, + }, + + .num_resources = ARRAY_SIZE(u8500_shrm_resources), + .resource = u8500_shrm_resources +}; + +#ifdef CONFIG_FB_MCDE +static struct resource mcde_resources[] = { + [0] = { + .name = MCDE_IO_AREA, + .start = U8500_MCDE_BASE, + .end = U8500_MCDE_BASE + U8500_MCDE_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = MCDE_IO_AREA, + .start = U8500_DSI_LINK1_BASE, + .end = U8500_DSI_LINK1_BASE + U8500_DSI_LINK_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [2] = { + .name = MCDE_IO_AREA, + .start = U8500_DSI_LINK2_BASE, + .end = U8500_DSI_LINK2_BASE + U8500_DSI_LINK_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [3] = { + .name = MCDE_IO_AREA, + .start = U8500_DSI_LINK3_BASE, + .end = U8500_DSI_LINK3_BASE + U8500_DSI_LINK_SIZE - 1, + .flags = IORESOURCE_MEM, + }, + [4] = { + .name = MCDE_IRQ, + .start = IRQ_DB8500_DISP, + .end = IRQ_DB8500_DISP, + .flags = IORESOURCE_IRQ, + }, +}; + +static int mcde_platform_enable_dsipll(void) +{ + return prcmu_enable_dsipll(); +} + +static int mcde_platform_disable_dsipll(void) +{ + return prcmu_disable_dsipll(); +} + +static int mcde_platform_set_display_clocks(void) +{ + return prcmu_set_display_clocks(); +} - .src_info.data_width = STEDMA40_BYTE_WIDTH, - .src_info.psize = STEDMA40_PSIZE_PHY_1, - .src_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL, +static struct mcde_platform_data mcde_u8500_pdata = { + .num_dsilinks = 3, + /* + * [0] = 3: 24 bits DPI: connect LSB Ch B to D[0:7] + * [3] = 4: 24 bits DPI: connect MID Ch B to D[24:31] + * [4] = 5: 24 bits DPI: connect MSB Ch B to D[32:39] + * + * [1] = 3: TV out : connect LSB Ch B to D[8:15] + */ +#define DONT_CARE 0 + .outmux = { 3, 3, DONT_CARE, 4, 5 }, +#undef DONT_CARE + .syncmux = 0x00, /* DPI channel A and B on output pins A and B resp */ + .num_channels = 4, + .num_overlays = 6, +#ifdef CONFIG_MCDE_DISPLAY_DSI + .regulator_vana_id = "vdddsi1v2", +#endif + .regulator_mcde_epod_id = "vsupply", + .regulator_esram_epod_id = "v-esram34", +#ifdef CONFIG_MCDE_DISPLAY_DSI + .clock_dsi_id = "hdmi", + .clock_dsi_lp_id = "tv", +#endif + .clock_dpi_id = "lcd", + .clock_mcde_id = "mcde", + .platform_set_clocks = mcde_platform_set_display_clocks, + .platform_enable_dsipll = mcde_platform_enable_dsipll, + .platform_disable_dsipll = mcde_platform_disable_dsipll, +}; - .dst_info.data_width = STEDMA40_BYTE_WIDTH, - .dst_info.psize = STEDMA40_PSIZE_PHY_1, - .dst_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL, +struct platform_device u8500_mcde_device = { + .name = "mcde", + .id = -1, + .dev = { + .platform_data = &mcde_u8500_pdata, + }, + .num_resources = ARRAY_SIZE(mcde_resources), + .resource = mcde_resources, }; -/* Default configuration for logical memcpy */ -struct stedma40_chan_cfg dma40_memcpy_conf_log = { - .dir = STEDMA40_MEM_TO_MEM, +#endif /* CONFIG_FB_MCDE */ - .src_info.data_width = STEDMA40_BYTE_WIDTH, - .src_info.psize = STEDMA40_PSIZE_LOG_1, - .src_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL, +static struct resource b2r2_resources[] = { + [0] = { + .start = U8500_B2R2_BASE, + .end = U8500_B2R2_BASE + ((4*1024)-1), + .name = "b2r2_base", + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "B2R2_IRQ", + .start = IRQ_DB8500_B2R2, + .end = IRQ_DB8500_B2R2, + .flags = IORESOURCE_IRQ, + }, +}; - .dst_info.data_width = STEDMA40_BYTE_WIDTH, - .dst_info.psize = STEDMA40_PSIZE_LOG_1, - .dst_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL, +struct platform_device u8500_b2r2_device = { + .name = "b2r2", + .id = 0, + .dev = { + .init_name = "b2r2_bus", + .coherent_dma_mask = ~0, + }, + .num_resources = ARRAY_SIZE(b2r2_resources), + .resource = b2r2_resources, }; /* - * Mapping between destination event lines and physical device address. - * The event line is tied to a device and therefore the address is constant. - * When the address comes from a primecell it will be configured in runtime - * and we set the address to -1 as a placeholder. + * WATCHDOG */ -static const dma_addr_t dma40_tx_map[DB8500_DMA_NR_DEV] = { - /* MUSB - these will be runtime-reconfigured */ - [DB8500_DMA_DEV39_USB_OTG_OEP_8] = -1, - [DB8500_DMA_DEV16_USB_OTG_OEP_7_15] = -1, - [DB8500_DMA_DEV17_USB_OTG_OEP_6_14] = -1, - [DB8500_DMA_DEV18_USB_OTG_OEP_5_13] = -1, - [DB8500_DMA_DEV19_USB_OTG_OEP_4_12] = -1, - [DB8500_DMA_DEV36_USB_OTG_OEP_3_11] = -1, - [DB8500_DMA_DEV37_USB_OTG_OEP_2_10] = -1, - [DB8500_DMA_DEV38_USB_OTG_OEP_1_9] = -1, - /* PrimeCells - run-time configured */ - [DB8500_DMA_DEV0_SPI0_TX] = -1, - [DB8500_DMA_DEV1_SD_MMC0_TX] = -1, - [DB8500_DMA_DEV2_SD_MMC1_TX] = -1, - [DB8500_DMA_DEV3_SD_MMC2_TX] = -1, - [DB8500_DMA_DEV8_SSP0_TX] = -1, - [DB8500_DMA_DEV9_SSP1_TX] = -1, - [DB8500_DMA_DEV11_UART2_TX] = -1, - [DB8500_DMA_DEV12_UART1_TX] = -1, - [DB8500_DMA_DEV13_UART0_TX] = -1, - [DB8500_DMA_DEV28_SD_MM2_TX] = -1, - [DB8500_DMA_DEV29_SD_MM0_TX] = -1, - [DB8500_DMA_DEV32_SD_MM1_TX] = -1, - [DB8500_DMA_DEV33_SPI2_TX] = -1, - [DB8500_DMA_DEV35_SPI1_TX] = -1, - [DB8500_DMA_DEV40_SPI3_TX] = -1, - [DB8500_DMA_DEV41_SD_MM3_TX] = -1, - [DB8500_DMA_DEV42_SD_MM4_TX] = -1, - [DB8500_DMA_DEV43_SD_MM5_TX] = -1, -}; - -/* Mapping between source event lines and physical device address */ -static const dma_addr_t dma40_rx_map[DB8500_DMA_NR_DEV] = { - /* MUSB - these will be runtime-reconfigured */ - [DB8500_DMA_DEV39_USB_OTG_IEP_8] = -1, - [DB8500_DMA_DEV16_USB_OTG_IEP_7_15] = -1, - [DB8500_DMA_DEV17_USB_OTG_IEP_6_14] = -1, - [DB8500_DMA_DEV18_USB_OTG_IEP_5_13] = -1, - [DB8500_DMA_DEV19_USB_OTG_IEP_4_12] = -1, - [DB8500_DMA_DEV36_USB_OTG_IEP_3_11] = -1, - [DB8500_DMA_DEV37_USB_OTG_IEP_2_10] = -1, - [DB8500_DMA_DEV38_USB_OTG_IEP_1_9] = -1, - /* PrimeCells */ - [DB8500_DMA_DEV0_SPI0_RX] = -1, - [DB8500_DMA_DEV1_SD_MMC0_RX] = -1, - [DB8500_DMA_DEV2_SD_MMC1_RX] = -1, - [DB8500_DMA_DEV3_SD_MMC2_RX] = -1, - [DB8500_DMA_DEV8_SSP0_RX] = -1, - [DB8500_DMA_DEV9_SSP1_RX] = -1, - [DB8500_DMA_DEV11_UART2_RX] = -1, - [DB8500_DMA_DEV12_UART1_RX] = -1, - [DB8500_DMA_DEV13_UART0_RX] = -1, - [DB8500_DMA_DEV28_SD_MM2_RX] = -1, - [DB8500_DMA_DEV29_SD_MM0_RX] = -1, - [DB8500_DMA_DEV32_SD_MM1_RX] = -1, - [DB8500_DMA_DEV33_SPI2_RX] = -1, - [DB8500_DMA_DEV35_SPI1_RX] = -1, - [DB8500_DMA_DEV40_SPI3_RX] = -1, - [DB8500_DMA_DEV41_SD_MM3_RX] = -1, - [DB8500_DMA_DEV42_SD_MM4_RX] = -1, - [DB8500_DMA_DEV43_SD_MM5_RX] = -1, -}; - -/* Reserved event lines for memcpy only */ -static int dma40_memcpy_event[] = { - DB8500_DMA_MEMCPY_TX_0, - DB8500_DMA_MEMCPY_TX_1, - DB8500_DMA_MEMCPY_TX_2, - DB8500_DMA_MEMCPY_TX_3, - DB8500_DMA_MEMCPY_TX_4, - DB8500_DMA_MEMCPY_TX_5, -}; - -static struct stedma40_platform_data dma40_plat_data = { - .dev_len = DB8500_DMA_NR_DEV, - .dev_rx = dma40_rx_map, - .dev_tx = dma40_tx_map, - .memcpy = dma40_memcpy_event, - .memcpy_len = ARRAY_SIZE(dma40_memcpy_event), - .memcpy_conf_phy = &dma40_memcpy_conf_phy, - .memcpy_conf_log = &dma40_memcpy_conf_log, - .disabled_channels = {-1}, -}; - -struct platform_device u8500_dma40_device = { - .dev = { - .platform_data = &dma40_plat_data, + +static struct resource u8500_wdt_resources[] = { + [0] = { + .start = U8500_TWD_BASE, + .end = U8500_TWD_BASE+0x37, + .flags = IORESOURCE_MEM, }, - .name = "dma40", - .id = 0, - .num_resources = ARRAY_SIZE(dma40_resources), - .resource = dma40_resources + [1] = { + .start = IRQ_LOCALWDOG, + .end = IRQ_LOCALWDOG, + .flags = IORESOURCE_IRQ, + } }; -void dma40_u8500ed_fixup(void) -{ - dma40_plat_data.memcpy = NULL; - dma40_plat_data.memcpy_len = 0; - dma40_resources[0].start = U8500_DMA_BASE_ED; - dma40_resources[0].end = U8500_DMA_BASE_ED + SZ_4K - 1; - dma40_resources[1].start = U8500_DMA_LCPA_BASE_ED; - dma40_resources[1].end = U8500_DMA_LCPA_BASE_ED + 2 * SZ_1K - 1; +struct platform_device u8500_wdt_device = { + .name = "mpcore_wdt", + .id = -1, + .resource = u8500_wdt_resources, + .num_resources = ARRAY_SIZE(u8500_wdt_resources), +}; + +struct platform_device u8500_prcmu_wdt_device = { + .name = "ux500_wdt", + .id = -1, +}; + +#ifdef CONFIG_HSI +/* + * HSI + */ +#define HSIR_OVERRUN(num) { \ + .start = IRQ_DB8500_HSIR_CH##num##_OVRRUN, \ + .end = IRQ_DB8500_HSIR_CH##num##_OVRRUN, \ + .flags = IORESOURCE_IRQ, \ + .name = "hsi_rx_overrun_ch"#num \ } +#define STE_HSI_PORT0_TX_CHANNEL_CFG(n) { \ + .dir = STEDMA40_MEM_TO_PERIPH, \ + .high_priority = true, \ + .mode = STEDMA40_MODE_LOGICAL, \ + .mode_opt = STEDMA40_LCHAN_SRC_LOG_DST_LOG, \ + .src_dev_type = STEDMA40_DEV_SRC_MEMORY, \ + .dst_dev_type = n,\ + .src_info.big_endian = false,\ + .src_info.data_width = STEDMA40_WORD_WIDTH,\ + .dst_info.big_endian = false,\ + .dst_info.data_width = STEDMA40_WORD_WIDTH,\ +}, + +#define STE_HSI_PORT0_RX_CHANNEL_CFG(n) { \ + .dir = STEDMA40_PERIPH_TO_MEM, \ + .high_priority = true, \ + .mode = STEDMA40_MODE_LOGICAL, \ + .mode_opt = STEDMA40_LCHAN_SRC_LOG_DST_LOG, \ + .src_dev_type = n,\ + .dst_dev_type = STEDMA40_DEV_DST_MEMORY, \ + .src_info.big_endian = false,\ + .src_info.data_width = STEDMA40_WORD_WIDTH,\ + .dst_info.big_endian = false,\ + .dst_info.data_width = STEDMA40_WORD_WIDTH,\ +}, + +static struct resource u8500_hsi_resources[] = { + { + .start = U8500_HSIR_BASE, + .end = U8500_HSIR_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + .name = "hsi_rx_base" + }, + { + .start = U8500_HSIT_BASE, + .end = U8500_HSIT_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + .name = "hsi_tx_base" + }, + { + .start = IRQ_DB8500_HSIRD0, + .end = IRQ_DB8500_HSIRD0, + .flags = IORESOURCE_IRQ, + .name = "hsi_rx_irq0" + }, + { + .start = IRQ_DB8500_HSITD0, + .end = IRQ_DB8500_HSITD0, + .flags = IORESOURCE_IRQ, + .name = "hsi_tx_irq0" + }, + { + .start = IRQ_DB8500_HSIR_EXCEP, + .end = IRQ_DB8500_HSIR_EXCEP, + .flags = IORESOURCE_IRQ, + .name = "hsi_rx_excep0" + }, + HSIR_OVERRUN(0), + HSIR_OVERRUN(1), + HSIR_OVERRUN(2), + HSIR_OVERRUN(3), + HSIR_OVERRUN(4), + HSIR_OVERRUN(5), + HSIR_OVERRUN(6), + HSIR_OVERRUN(7), +}; + +#ifdef CONFIG_STE_DMA40 +static struct stedma40_chan_cfg ste_hsi_port0_dma_tx_cfg[] = { + STE_HSI_PORT0_TX_CHANNEL_CFG(DB8500_DMA_DEV20_SLIM0_CH0_TX_HSI_TX_CH0) + STE_HSI_PORT0_TX_CHANNEL_CFG(DB8500_DMA_DEV21_SLIM0_CH1_TX_HSI_TX_CH1) + STE_HSI_PORT0_TX_CHANNEL_CFG(DB8500_DMA_DEV22_SLIM0_CH2_TX_HSI_TX_CH2) + STE_HSI_PORT0_TX_CHANNEL_CFG(DB8500_DMA_DEV23_SLIM0_CH3_TX_HSI_TX_CH3) +}; + +static struct stedma40_chan_cfg ste_hsi_port0_dma_rx_cfg[] = { + STE_HSI_PORT0_RX_CHANNEL_CFG(DB8500_DMA_DEV20_SLIM0_CH0_RX_HSI_RX_CH0) + STE_HSI_PORT0_RX_CHANNEL_CFG(DB8500_DMA_DEV21_SLIM0_CH1_RX_HSI_RX_CH1) + STE_HSI_PORT0_RX_CHANNEL_CFG(DB8500_DMA_DEV22_SLIM0_CH2_RX_HSI_RX_CH2) + STE_HSI_PORT0_RX_CHANNEL_CFG(DB8500_DMA_DEV23_SLIM0_CH3_RX_HSI_RX_CH3) +}; +#endif + +static struct ste_hsi_port_cfg ste_hsi_port0_cfg = { +#ifdef CONFIG_STE_DMA40 + .dma_filter = stedma40_filter, + .dma_tx_cfg = ste_hsi_port0_dma_tx_cfg, + .dma_rx_cfg = ste_hsi_port0_dma_rx_cfg +#endif +}; + +struct ste_hsi_platform_data u8500_hsi_platform_data = { + .num_ports = 1, + .use_dma = 1, + .port_cfg = &ste_hsi_port0_cfg, +}; + +struct platform_device u8500_hsi_device = { + .dev = { + .platform_data = &u8500_hsi_platform_data, + }, + .name = "ste_hsi", + .id = 0, + .resource = u8500_hsi_resources, + .num_resources = ARRAY_SIZE(u8500_hsi_resources) +}; +#endif /* CONFIG_HSI */ + +/* + * Thermal Sensor + */ + +static struct resource u8500_thsens_resources[] = { + { + .name = "IRQ_HOTMON_LOW", + .start = IRQ_PRCMU_HOTMON_LOW, + .end = IRQ_PRCMU_HOTMON_LOW, + .flags = IORESOURCE_IRQ, + }, + { + .name = "IRQ_HOTMON_HIGH", + .start = IRQ_PRCMU_HOTMON_HIGH, + .end = IRQ_PRCMU_HOTMON_HIGH, + .flags = IORESOURCE_IRQ, + }, +}; + +struct platform_device u8500_thsens_device = { + .name = "dbx500_temp", + .resource = u8500_thsens_resources, + .num_resources = ARRAY_SIZE(u8500_thsens_resources), +}; + struct resource keypad_resources[] = { [0] = { .start = U8500_SKE_BASE, diff --git a/arch/arm/mach-ux500/devices-db8500.h b/arch/arm/mach-ux500/devices-db8500.h index cbd4a9ae810..4a54a6f5aa6 100644 --- a/arch/arm/mach-ux500/devices-db8500.h +++ b/arch/arm/mach-ux500/devices-db8500.h @@ -98,4 +98,9 @@ db8500_add_ssp(const char *name, resource_size_t base, int irq, #define db8500_add_uart2(pdata) \ dbx500_add_uart("uart2", U8500_UART2_BASE, IRQ_DB8500_UART2, pdata) +#define db8500_add_cryp1(pdata) \ + dbx500_add_cryp1(-1, U8500_CRYP1_BASE, IRQ_DB8500_CRYP1, pdata) +#define db8500_add_hash1(pdata) \ + dbx500_add_hash1(-1, U8500_HASH1_BASE, pdata) + #endif diff --git a/arch/arm/mach-ux500/devices.c b/arch/arm/mach-ux500/devices.c index ea0a2f92ca7..151be4f3e68 100644 --- a/arch/arm/mach-ux500/devices.c +++ b/arch/arm/mach-ux500/devices.c @@ -14,6 +14,51 @@ #include <mach/hardware.h> #include <mach/setup.h> +#ifdef CONFIG_STE_TRACE_MODEM +#include <linux/db8500-modem-trace.h> +#endif + +#ifdef CONFIG_STE_TRACE_MODEM +static struct resource trace_resource = { + .start = 0, + .end = 0, + .name = "db8500-trace-area", + .flags = IORESOURCE_MEM +}; + +static struct db8500_trace_platform_data trace_pdata = { + .ape_base = U8500_APE_BASE, + .modem_base = U8500_MODEM_BASE, +}; + +struct platform_device u8500_trace_modem = { + .name = "db8500-modem-trace", + .id = 0, + .dev = { + .init_name = "db8500-modem-trace", + .platform_data = &trace_pdata, + }, + .num_resources = 1, + .resource = &trace_resource, +}; + +static int __init early_trace_modem(char *p) +{ + struct resource *data = &trace_resource; + u32 size = memparse(p, &p); + if (*p == '@') + data->start = memparse(p + 1, &p); + data->end = data->start + size -1; + return 0; +} + +early_param("mem_mtrace", early_trace_modem); +#endif + +struct platform_device ux500_hwmem_device = { + .name = "hwmem", +}; + void __init amba_add_devices(struct amba_device *devs[], int num) { int i; diff --git a/arch/arm/mach-ux500/dma-db5500.c b/arch/arm/mach-ux500/dma-db5500.c index 1cfab68ae41..d71c489d022 100644 --- a/arch/arm/mach-ux500/dma-db5500.c +++ b/arch/arm/mach-ux500/dma-db5500.c @@ -14,8 +14,7 @@ #include <plat/ste_dma40.h> #include <mach/setup.h> #include <mach/hardware.h> - -#include "ste-dma40-db5500.h" +#include <mach/ste-dma40-db5500.h> static struct resource dma40_resources[] = { [0] = { @@ -72,28 +71,128 @@ static struct stedma40_chan_cfg dma40_memcpy_conf_log = { * now. */ static const dma_addr_t dma40_rx_map[DB5500_DMA_NR_DEV] = { - [DB5500_DMA_DEV24_SDMMC0_RX] = -1, - [DB5500_DMA_DEV38_USB_OTG_IEP_8] = -1, - [DB5500_DMA_DEV23_USB_OTG_IEP_7_15] = -1, - [DB5500_DMA_DEV22_USB_OTG_IEP_6_14] = -1, - [DB5500_DMA_DEV21_USB_OTG_IEP_5_13] = -1, - [DB5500_DMA_DEV20_USB_OTG_IEP_4_12] = -1, - [DB5500_DMA_DEV6_USB_OTG_IEP_3_11] = -1, - [DB5500_DMA_DEV5_USB_OTG_IEP_2_10] = -1, - [DB5500_DMA_DEV4_USB_OTG_IEP_1_9] = -1, + [DB5500_DMA_DEV0_SPI0_RX] = 0, + [DB5500_DMA_DEV1_SPI1_RX] = 0, + [DB5500_DMA_DEV2_SPI2_RX] = 0, + [DB5500_DMA_DEV3_SPI3_RX] = 0, + [DB5500_DMA_DEV4_USB_OTG_IEP_1_9] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV5_USB_OTG_IEP_2_10] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV6_USB_OTG_IEP_3_11] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV7_IRDA_RFS] = 0, + [DB5500_DMA_DEV8_IRDA_FIFO_RX] = 0, + [DB5500_DMA_DEV9_MSP0_RX] = U5500_MSP0_BASE + MSP_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV10_MSP1_RX] = U5500_MSP1_BASE + MSP_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV11_MSP2_RX] = U5500_MSP2_BASE + MSP_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV12_UART0_RX] = 0, + [DB5500_DMA_DEV13_UART1_RX] = 0, + [DB5500_DMA_DEV14_UART2_RX] = 0, + [DB5500_DMA_DEV15_UART3_RX] = 0, + [DB5500_DMA_DEV16_USB_OTG_IEP_8] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV17_USB_OTG_IEP_1_9] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV18_USB_OTG_IEP_2_10] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV19_USB_OTG_IEP_3_11] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV20_USB_OTG_IEP_4_12] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV21_USB_OTG_IEP_5_13] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV22_USB_OTG_IEP_6_14] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV23_USB_OTG_IEP_7_15] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV24_SDMMC0_RX] = U5500_SDI0_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV25_SDMMC1_RX] = U5500_SDI1_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV26_SDMMC2_RX] = U5500_SDI2_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV27_SDMMC3_RX] = U5500_SDI3_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV28_SDMMC4_RX] = U5500_SDI4_BASE + SD_MMC_TX_RX_REG_OFFSET, + /* 29, 30 not used */ + [DB5500_DMA_DEV31_CRYPTO1_RX] = 0, /* v2 */ + /* 32 not used */ + [DB5500_DMA_DEV33_SDMMC0_RX] = U5500_SDI0_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV34_SDMMC1_RX] = U5500_SDI1_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV35_SDMMC2_RX] = U5500_SDI2_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV36_SDMMC3_RX] = U5500_SDI3_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV37_SDMMC4_RX] = U5500_SDI4_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV38_USB_OTG_IEP_8] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV39_USB_OTG_IEP_1_9] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV40_USB_OTG_IEP_2_10] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV41_USB_OTG_IEP_3_11] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV42_USB_OTG_IEP_4_12] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV43_USB_OTG_IEP_5_13] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV44_USB_OTG_IEP_6_14] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV45_USB_OTG_IEP_7_15] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV46_CRYPTO1_RX] = 0, /* v2 */ + [DB5500_DMA_DEV47_MCDE_RX] = 0, + [DB5500_DMA_DEV48_CRYPTO1_RX] = U5500_CRYP1_BASE + CRYP1_RX_REG_OFFSET, + /* 49, 50 not used */ + [DB5500_DMA_DEV49_I2C1_RX] = 0, + [DB5500_DMA_DEV50_I2C3_RX] = 0, + [DB5500_DMA_DEV51_I2C2_RX] = 0, + /* 54 - 60 not used */ + [DB5500_DMA_DEV61_CRYPTO0_RX] = 0, + /* 62, 63 not used */ }; /* Mapping between destination event lines and physical device address */ static const dma_addr_t dma40_tx_map[DB5500_DMA_NR_DEV] = { - [DB5500_DMA_DEV24_SDMMC0_TX] = -1, - [DB5500_DMA_DEV38_USB_OTG_OEP_8] = -1, - [DB5500_DMA_DEV23_USB_OTG_OEP_7_15] = -1, - [DB5500_DMA_DEV22_USB_OTG_OEP_6_14] = -1, - [DB5500_DMA_DEV21_USB_OTG_OEP_5_13] = -1, - [DB5500_DMA_DEV20_USB_OTG_OEP_4_12] = -1, - [DB5500_DMA_DEV6_USB_OTG_OEP_3_11] = -1, - [DB5500_DMA_DEV5_USB_OTG_OEP_2_10] = -1, - [DB5500_DMA_DEV4_USB_OTG_OEP_1_9] = -1, + [DB5500_DMA_DEV0_SPI0_TX] = 0, + [DB5500_DMA_DEV1_SPI1_TX] = 0, + [DB5500_DMA_DEV2_SPI2_TX] = 0, + [DB5500_DMA_DEV3_SPI3_TX] = 0, + [DB5500_DMA_DEV4_USB_OTG_OEP_1_9] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV5_USB_OTG_OEP_2_10] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV6_USB_OTG_OEP_3_11] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV7_IRRC_TX] = 0, + [DB5500_DMA_DEV8_IRDA_FIFO_TX] = 0, + [DB5500_DMA_DEV9_MSP0_TX] = U5500_MSP0_BASE + MSP_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV10_MSP1_TX] = U5500_MSP1_BASE + MSP_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV11_MSP2_TX] = U5500_MSP2_BASE + MSP_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV12_UART0_TX] = 0, + [DB5500_DMA_DEV13_UART1_TX] = 0, + [DB5500_DMA_DEV14_UART2_TX] = 0, + [DB5500_DMA_DEV15_UART3_TX] = 0, + [DB5500_DMA_DEV16_USB_OTG_OEP_8] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV17_USB_OTG_OEP_1_9] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV18_USB_OTG_OEP_2_10] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV19_USB_OTG_OEP_3_11] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV20_USB_OTG_OEP_4_12] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV21_USB_OTG_OEP_5_13] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV22_USB_OTG_OEP_6_14] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV23_USB_OTG_OEP_7_15] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV24_SDMMC0_TX] = U5500_SDI0_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV25_SDMMC1_TX] = U5500_SDI1_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV26_SDMMC2_TX] = U5500_SDI2_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV27_SDMMC3_TX] = U5500_SDI3_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV28_SDMMC4_TX] = U5500_SDI4_BASE + SD_MMC_TX_RX_REG_OFFSET, + /* 29 not used */ + [DB5500_DMA_DEV30_HASH1_TX] = 0, /* v2 */ + [DB5500_DMA_DEV31_CRYPTO1_TX] = 0, /* v2 */ + [DB5500_DMA_DEV32_FSMC_TX] = 0, + [DB5500_DMA_DEV33_SDMMC0_TX] = U5500_SDI0_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV34_SDMMC1_TX] = U5500_SDI1_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV35_SDMMC2_TX] = U5500_SDI2_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV36_SDMMC3_TX] = U5500_SDI3_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV37_SDMMC4_TX] = U5500_SDI4_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB5500_DMA_DEV38_USB_OTG_OEP_8] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV39_USB_OTG_OEP_1_9] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV40_USB_OTG_OEP_2_10] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV41_USB_OTG_OEP_3_11] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV42_USB_OTG_OEP_4_12] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV43_USB_OTG_OEP_5_13] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV44_USB_OTG_OEP_6_14] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV45_USB_OTG_OEP_7_15] = U5500_USBOTG_BASE, + [DB5500_DMA_DEV46_CRYPTO1_TX] = 0, /* v2 */ + [DB5500_DMA_DEV47_STM_TX] = 0, + [DB5500_DMA_DEV48_CRYPTO1_TX] = U5500_CRYP1_BASE + CRYP1_TX_REG_OFFSET, + [DB5500_DMA_DEV49_CRYPTO1_TX_HASH1_TX] = 0, + [DB5500_DMA_DEV50_HASH1_TX] = U5500_HASH1_BASE + HASH1_TX_REG_OFFSET, + [DB5500_DMA_DEV51_I2C1_TX] = 0, + [DB5500_DMA_DEV52_I2C3_TX] = 0, + [DB5500_DMA_DEV53_I2C2_TX] = 0, + /* 54, 55 not used */ + [DB5500_DMA_MEMCPY_TX_1] = 0, + [DB5500_DMA_MEMCPY_TX_2] = 0, + [DB5500_DMA_MEMCPY_TX_3] = 0, + [DB5500_DMA_MEMCPY_TX_4] = 0, + [DB5500_DMA_MEMCPY_TX_5] = 0, + [DB5500_DMA_DEV61_CRYPTO0_TX] = 0, + [DB5500_DMA_DEV62_CRYPTO0_TX_HASH0_TX] = 0, + [DB5500_DMA_DEV63_HASH0_TX] = 0, }; static int dma40_memcpy_event[] = { diff --git a/arch/arm/mach-ux500/dma-db8500.c b/arch/arm/mach-ux500/dma-db8500.c new file mode 100644 index 00000000000..9976478fd80 --- /dev/null +++ b/arch/arm/mach-ux500/dma-db8500.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) ST-Ericsson SA 2007-2010 + * + * Author: Per Friden <per.friden@stericsson.com> for ST-Ericsson + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/kernel.h> +#include <linux/platform_device.h> + +#include <plat/ste_dma40.h> + +#ifdef CONFIG_HSI +#include <mach/hsi.h> +#endif +#include <mach/setup.h> +#include <mach/ste-dma40-db8500.h> +#include <mach/pm.h> +#include <mach/context.h> + + + +static struct resource dma40_resources[] = { + [0] = { + .start = U8500_DMA_BASE, + .end = U8500_DMA_BASE + SZ_4K - 1, + .flags = IORESOURCE_MEM, + .name = "base", + }, + [1] = { + .start = U8500_DMA_LCPA_BASE, + .end = U8500_DMA_LCPA_BASE + 2 * SZ_1K - 1, + .flags = IORESOURCE_MEM, + .name = "lcpa", + }, + [2] = { + .start = IRQ_DB8500_DMA, + .end = IRQ_DB8500_DMA, + .flags = IORESOURCE_IRQ + }, + [3] = { + .start = U8500_DMA_LCLA_BASE, + .end = U8500_DMA_LCLA_BASE + SZ_8K - 1, + .flags = IORESOURCE_MEM, + .name = "lcla_esram", + } +}; + +/* Default configuration for physcial memcpy */ +static struct stedma40_chan_cfg dma40_memcpy_conf_phy = { + .mode = STEDMA40_MODE_PHYSICAL, + .dir = STEDMA40_MEM_TO_MEM, + + .src_info.data_width = STEDMA40_BYTE_WIDTH, + .src_info.psize = STEDMA40_PSIZE_PHY_1, + .src_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL, + + .dst_info.data_width = STEDMA40_BYTE_WIDTH, + .dst_info.psize = STEDMA40_PSIZE_PHY_1, + .dst_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL, + +}; + +/* Default configuration for logical memcpy */ +static struct stedma40_chan_cfg dma40_memcpy_conf_log = { + .dir = STEDMA40_MEM_TO_MEM, + + .src_info.data_width = STEDMA40_BYTE_WIDTH, + .src_info.psize = STEDMA40_PSIZE_LOG_1, + .src_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL, + + .dst_info.data_width = STEDMA40_BYTE_WIDTH, + .dst_info.psize = STEDMA40_PSIZE_LOG_1, + .dst_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL, + +}; + +/* + * Mapping between soruce event lines and physical device address + * This was created assuming that the event line is tied to a device and + * therefore the address is constant, however this is not true for at least + * USB, and the values are just placeholders for USB. This table is preserved + * and used for now. + */ +static dma_addr_t dma40_rx_map[DB8500_DMA_NR_DEV] = { + [DB8500_DMA_DEV0_SPI0_RX] = 0, + [DB8500_DMA_DEV1_SD_MMC0_RX] = U8500_SDI0_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV2_SD_MMC1_RX] = 0, + [DB8500_DMA_DEV3_SD_MMC2_RX] = 0, + [DB8500_DMA_DEV4_I2C1_RX] = 0, + [DB8500_DMA_DEV5_I2C3_RX] = 0, + [DB8500_DMA_DEV6_I2C2_RX] = 0, + [DB8500_DMA_DEV7_I2C4_RX] = 0, + [DB8500_DMA_DEV8_SSP0_RX] = U8500_SSP0_BASE + SSP_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV9_SSP1_RX] = 0, + [DB8500_DMA_DEV10_MCDE_RX] = 0, + [DB8500_DMA_DEV11_UART2_RX] = 0, + [DB8500_DMA_DEV12_UART1_RX] = 0, + [DB8500_DMA_DEV13_UART0_RX] = 0, + [DB8500_DMA_DEV14_MSP2_RX] = U8500_MSP2_BASE + MSP_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV15_I2C0_RX] = 0, + [DB8500_DMA_DEV16_USB_OTG_IEP_7_15] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV17_USB_OTG_IEP_6_14] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV18_USB_OTG_IEP_5_13] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV19_USB_OTG_IEP_4_12] = U8500_USBOTG_BASE, +#ifdef CONFIG_HSI + [DB8500_DMA_DEV20_SLIM0_CH0_RX_HSI_RX_CH0] = U8500_HSIR_BASE + 0x0 + STE_HSI_RX_BUFFERX, + [DB8500_DMA_DEV21_SLIM0_CH1_RX_HSI_RX_CH1] = U8500_HSIR_BASE + 0x4 + STE_HSI_RX_BUFFERX, + [DB8500_DMA_DEV22_SLIM0_CH2_RX_HSI_RX_CH2] = U8500_HSIR_BASE + 0x8 + STE_HSI_RX_BUFFERX, + [DB8500_DMA_DEV23_SLIM0_CH3_RX_HSI_RX_CH3] = U8500_HSIR_BASE + 0xC + STE_HSI_RX_BUFFERX, +#endif + [DB8500_DMA_DEV24_SRC_SXA0_RX_TX] = 0, + [DB8500_DMA_DEV25_SRC_SXA1_RX_TX] = 0, + [DB8500_DMA_DEV26_SRC_SXA2_RX_TX] = 0, + [DB8500_DMA_DEV27_SRC_SXA3_RX_TX] = 0, + [DB8500_DMA_DEV28_SD_MM2_RX] = U8500_SDI2_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV29_SD_MM0_RX] = U8500_SDI0_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV30_MSP3_RX] = U8500_MSP3_BASE + MSP_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV31_MSP0_RX_SLIM0_CH0_RX] = U8500_MSP0_BASE + MSP_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV32_SD_MM1_RX] = U8500_SDI1_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV33_SPI2_RX] = 0, + [DB8500_DMA_DEV34_I2C3_RX2] = 0, + [DB8500_DMA_DEV35_SPI1_RX] = 0, + [DB8500_DMA_DEV36_USB_OTG_IEP_3_11] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV37_USB_OTG_IEP_2_10] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV38_USB_OTG_IEP_1_9] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV39_USB_OTG_IEP_8] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV40_SPI3_RX] = 0, + [DB8500_DMA_DEV41_SD_MM3_RX] = 0, + [DB8500_DMA_DEV42_SD_MM4_RX] = U8500_SDI4_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV43_SD_MM5_RX] = 0, + [DB8500_DMA_DEV44_SRC_SXA4_RX_TX] = 0, + [DB8500_DMA_DEV45_SRC_SXA5_RX_TX] = 0, + [DB8500_DMA_DEV46_SLIM0_CH8_RX_SRC_SXA6_RX_TX] = 0, + [DB8500_DMA_DEV47_SLIM0_CH9_RX_SRC_SXA7_RX_TX] = 0, + [DB8500_DMA_DEV48_CAC1_RX] = U8500_CRYP1_BASE + CRYP1_RX_REG_OFFSET, + /* 49, 50 and 51 are not used */ + [DB8500_DMA_DEV52_SLIM0_CH4_RX_HSI_RX_CH4] = 0, + [DB8500_DMA_DEV53_SLIM0_CH5_RX_HSI_RX_CH5] = 0, + [DB8500_DMA_DEV54_SLIM0_CH6_RX_HSI_RX_CH6] = 0, + [DB8500_DMA_DEV55_SLIM0_CH7_RX_HSI_RX_CH7] = 0, + /* 56, 57, 58, 59 and 60 are not used */ + [DB8500_DMA_DEV61_CAC0_RX] = 0, + /* 62 and 63 are not used */ +}; + +/* Mapping between destination event lines and physical device address */ +static const dma_addr_t dma40_tx_map[DB8500_DMA_NR_DEV] = { + [DB8500_DMA_DEV0_SPI0_TX] = 0, + [DB8500_DMA_DEV1_SD_MMC0_TX] = U8500_SDI0_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV2_SD_MMC1_TX] = 0, + [DB8500_DMA_DEV3_SD_MMC2_TX] = 0, + [DB8500_DMA_DEV4_I2C1_TX] = 0, + [DB8500_DMA_DEV5_I2C3_TX] = 0, + [DB8500_DMA_DEV6_I2C2_TX] = 0, + [DB8500_DMA_DEV7_I2C4_TX] = 0, + [DB8500_DMA_DEV8_SSP0_TX] = U8500_SSP0_BASE + SSP_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV9_SSP1_TX] = 0, + /* 10 is not used*/ + [DB8500_DMA_DEV11_UART2_TX] = 0, + [DB8500_DMA_DEV12_UART1_TX] = 0, + [DB8500_DMA_DEV13_UART0_TX] = 0, + [DB8500_DMA_DEV14_MSP2_TX] = U8500_MSP2_BASE + MSP_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV15_I2C0_TX] = 0, + [DB8500_DMA_DEV16_USB_OTG_OEP_7_15] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV17_USB_OTG_OEP_6_14] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV18_USB_OTG_OEP_5_13] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV19_USB_OTG_OEP_4_12] = U8500_USBOTG_BASE, +#ifdef CONFIG_HSI + [DB8500_DMA_DEV20_SLIM0_CH0_TX_HSI_TX_CH0] = U8500_HSIT_BASE + 0x0 + STE_HSI_TX_BUFFERX, + [DB8500_DMA_DEV21_SLIM0_CH1_TX_HSI_TX_CH1] = U8500_HSIT_BASE + 0x4 + STE_HSI_TX_BUFFERX, + [DB8500_DMA_DEV22_SLIM0_CH2_TX_HSI_TX_CH2] = U8500_HSIT_BASE + 0x8 + STE_HSI_TX_BUFFERX, + [DB8500_DMA_DEV23_SLIM0_CH3_TX_HSI_TX_CH3] = U8500_HSIT_BASE + 0xC + STE_HSI_TX_BUFFERX, +#endif + [DB8500_DMA_DEV24_DST_SXA0_RX_TX] = 0, + [DB8500_DMA_DEV25_DST_SXA1_RX_TX] = 0, + [DB8500_DMA_DEV26_DST_SXA2_RX_TX] = 0, + [DB8500_DMA_DEV27_DST_SXA3_RX_TX] = 0, + [DB8500_DMA_DEV28_SD_MM2_TX] = U8500_SDI2_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV29_SD_MM0_TX] = U8500_SDI0_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV30_MSP1_TX] = U8500_MSP1_BASE + MSP_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV31_MSP0_TX_SLIM0_CH0_TX] = U8500_MSP0_BASE + MSP_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV32_SD_MM1_TX] = U8500_SDI1_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV33_SPI2_TX] = 0, + [DB8500_DMA_DEV34_I2C3_TX2] = 0, + [DB8500_DMA_DEV35_SPI1_TX] = 0, + [DB8500_DMA_DEV36_USB_OTG_OEP_3_11] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV37_USB_OTG_OEP_2_10] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV38_USB_OTG_OEP_1_9] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV39_USB_OTG_OEP_8] = U8500_USBOTG_BASE, + [DB8500_DMA_DEV40_SPI3_TX] = 0, + [DB8500_DMA_DEV41_SD_MM3_TX] = 0, + [DB8500_DMA_DEV42_SD_MM4_TX] = U8500_SDI4_BASE + SD_MMC_TX_RX_REG_OFFSET, + [DB8500_DMA_DEV43_SD_MM5_TX] = 0, + [DB8500_DMA_DEV44_DST_SXA4_RX_TX] = 0, + [DB8500_DMA_DEV45_DST_SXA5_RX_TX] = 0, + [DB8500_DMA_DEV46_SLIM0_CH8_TX_DST_SXA6_RX_TX] = 0, + [DB8500_DMA_DEV47_SLIM0_CH9_TX_DST_SXA7_RX_TX] = 0, + [DB8500_DMA_DEV48_CAC1_TX] = U8500_CRYP1_BASE + CRYP1_TX_REG_OFFSET, + [DB8500_DMA_DEV49_CAC1_TX_HAC1_TX] = 0, + [DB8500_DMA_DEV50_HAC1_TX] = U8500_HASH1_BASE + HASH1_TX_REG_OFFSET, + [DB8500_DMA_MEMCPY_TX_0] = 0, + [DB8500_DMA_DEV52_SLIM1_CH4_TX_HSI_TX_CH4] = 0, + [DB8500_DMA_DEV53_SLIM1_CH5_TX_HSI_TX_CH5] = 0, + [DB8500_DMA_DEV54_SLIM1_CH6_TX_HSI_TX_CH6] = 0, + [DB8500_DMA_DEV55_SLIM1_CH7_TX_HSI_TX_CH7] = 0, + [DB8500_DMA_MEMCPY_TX_1] = 0, + [DB8500_DMA_MEMCPY_TX_2] = 0, + [DB8500_DMA_MEMCPY_TX_3] = 0, + [DB8500_DMA_MEMCPY_TX_4] = 0, + [DB8500_DMA_MEMCPY_TX_5] = 0, + [DB8500_DMA_DEV61_CAC0_TX] = 0, + [DB8500_DMA_DEV62_CAC0_TX_HAC0_TX] = 0, + [DB8500_DMA_DEV63_HAC0_TX] = 0, +}; + +/* Reserved event lines for memcpy only */ +static int dma40_memcpy_event[] = { + DB8500_DMA_MEMCPY_TX_0, + DB8500_DMA_MEMCPY_TX_1, + DB8500_DMA_MEMCPY_TX_2, + DB8500_DMA_MEMCPY_TX_3, + DB8500_DMA_MEMCPY_TX_4, + DB8500_DMA_MEMCPY_TX_5, +}; + +static struct stedma40_platform_data dma40_plat_data = { + .dev_len = ARRAY_SIZE(dma40_rx_map), + .dev_rx = dma40_rx_map, + .dev_tx = dma40_tx_map, + .memcpy = dma40_memcpy_event, + .memcpy_len = ARRAY_SIZE(dma40_memcpy_event), + .memcpy_conf_phy = &dma40_memcpy_conf_phy, + .memcpy_conf_log = &dma40_memcpy_conf_log, + /* Audio is using physical channel 2 from MMDSP */ + .disabled_channels = {2, -1}, + .use_esram_lcla = false, +}; + +#ifdef CONFIG_UX500_CONTEXT +#define D40_DREG_GCC 0x000 +#define D40_DREG_LCPA 0x020 +#define D40_DREG_LCLA 0x024 + +static void __iomem *base; + +static int dma_context_notifier_call(struct notifier_block *this, + unsigned long event, void *data) +{ + static unsigned long lcpa; + static unsigned long lcla; + static unsigned long gcc; + + switch (event) { + case CONTEXT_APE_SAVE: + lcla = readl(base + D40_DREG_LCLA); + lcpa = readl(base + D40_DREG_LCPA); + gcc = readl(base + D40_DREG_GCC); + break; + + case CONTEXT_APE_RESTORE: + writel(gcc, base + D40_DREG_GCC); + writel(lcpa, base + D40_DREG_LCPA); + writel(lcla, base + D40_DREG_LCLA); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block dma_context_notifier = { + .notifier_call = dma_context_notifier_call, +}; + +static void dma_context_notifier_init(void) +{ + base = ioremap(dma40_resources[0].start, resource_size(&dma40_resources[0])); + if (WARN_ON(!base)) + return; + + WARN_ON(context_ape_notifier_register(&dma_context_notifier)); +} +#else +static void dma_context_notifier_init(void) +{ +} +#endif + +static struct platform_device dma40_device = { + .dev = { + .platform_data = &dma40_plat_data, +#ifdef CONFIG_PM + .pm_domain = &ux500_dev_power_domain, +#endif + }, + .name = "dma40", + .id = 0, + .num_resources = ARRAY_SIZE(dma40_resources), + .resource = dma40_resources +}; + +void __init db8500_dma_init(void) +{ + int ret; + + ret = platform_device_register(&dma40_device); + if (ret) + dev_err(&dma40_device.dev, "unable to register device: %d\n", ret); + + dma_context_notifier_init(); +} diff --git a/arch/arm/mach-ux500/hotplug.c b/arch/arm/mach-ux500/hotplug.c index dd8037ebccf..0cf4798aa07 100644 --- a/arch/arm/mach-ux500/hotplug.c +++ b/arch/arm/mach-ux500/hotplug.c @@ -11,22 +11,33 @@ #include <linux/kernel.h> #include <linux/errno.h> #include <linux/smp.h> +#include <linux/completion.h> #include <asm/cacheflush.h> +#include <mach/context.h> + extern volatile int pen_release; +static DECLARE_COMPLETION(cpu_killed); + static inline void platform_do_lowpower(unsigned int cpu) { flush_cache_all(); - /* we put the platform to just WFI */ for (;;) { - __asm__ __volatile__("dsb\n\t" "wfi\n\t" - : : : "memory"); + + context_varm_save_core(); + context_save_cpu_registers(); + + context_save_to_sram_and_wfi(false); + + context_restore_cpu_registers(); + context_varm_restore_core(); + if (pen_release == cpu) { /* - * OK, proper wakeup, we're done + * OK, proper wakeup, we're done */ break; } @@ -35,7 +46,7 @@ static inline void platform_do_lowpower(unsigned int cpu) int platform_cpu_kill(unsigned int cpu) { - return 1; + return wait_for_completion_timeout(&cpu_killed, 5000); } /* @@ -45,6 +56,19 @@ int platform_cpu_kill(unsigned int cpu) */ void platform_cpu_die(unsigned int cpu) { +#ifdef DEBUG + unsigned int this_cpu = hard_smp_processor_id(); + + if (cpu != this_cpu) { + printk(KERN_CRIT "Eek! platform_cpu_die running on %u, should be %u\n", + this_cpu, cpu); + BUG(); + } +#endif + + printk(KERN_NOTICE "CPU%u: shutdown\n", cpu); + complete(&cpu_killed); + /* directly enter low power state, skipping secure registers */ platform_do_lowpower(cpu); } diff --git a/arch/arm/mach-ux500/include/mach/ab8500_gpadc.h b/arch/arm/mach-ux500/include/mach/ab8500_gpadc.h new file mode 100644 index 00000000000..4289dcfc0aa --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/ab8500_gpadc.h @@ -0,0 +1,36 @@ +/* + * ab8500_gpadc.c - AB8500 GPADC Driver + * + * Copyright (C) 2010 ST-Ericsson SA + * Licensed under GPLv2. + * + * Author: Arun R Murthy <arun.murthy@stericsson.com> + */ + +#ifndef _AB8500_GPADC_H +#define _Ab8500_GPADC_H + +/* GPADC source: From datasheer(ADCSwSel[4:0] in GPADCCtrl2) */ +#define BAT_CTRL 0x01 +#define ACC_DETECT1 0x04 +#define ACC_DETECT2 0x05 +#define MAIN_BAT_V 0x08 +#define BK_BAT_V 0x0C +#define VBUS_V 0x09 +#define MAIN_CHARGER_V 0x03 +#define MAIN_CHARGER_C 0x0A +#define USB_CHARGER_C 0x0B +#define DIE_TEMP 0x0D +#define BTEMP_BALL 0x02 + +struct ab8500_gpadc_device_info { + struct completion ab8500_gpadc_complete; + struct mutex ab8500_gpadc_lock; +#if defined(CONFIG_REGULATOR) + struct regulator *regu; +#endif +}; + +int ab8500_gpadc_conversion(int input); + +#endif /* _AB8500_GPADC_H */ diff --git a/arch/arm/mach-ux500/include/mach/context.h b/arch/arm/mach-ux500/include/mach/context.h new file mode 100644 index 00000000000..22b56351284 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/context.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com> + * Rickard Andersson <rickard.andersson@stericsson.com> for + * ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + * + */ +#ifndef CONTEXT_H +#define CONTEXT_H + +#include <linux/notifier.h> + +#ifdef CONFIG_UX500_CONTEXT + +/* Defines to be with + * context_ape_notifier_register + */ +#define CONTEXT_APE_SAVE 0 /* APE save */ +#define CONTEXT_APE_RESTORE 1 /* APE restore */ + +/* Defines to be with + * context_arm_notifier_register + */ +#define CONTEXT_ARM_CORE_SAVE 0 /* Called for each ARM core */ +#define CONTEXT_ARM_CORE_RESTORE 1 /* Called for each ARM core */ +#define CONTEXT_ARM_COMMON_SAVE 2 /* Called when ARM common is saved */ +#define CONTEXT_ARM_COMMON_RESTORE 3 /* Called when ARM common is restored */ + +int context_ape_notifier_register(struct notifier_block *nb); +int context_ape_notifier_unregister(struct notifier_block *nb); + +int context_arm_notifier_register(struct notifier_block *nb); +int context_arm_notifier_unregister(struct notifier_block *nb); + +void context_vape_save(void); +void context_vape_restore(void); + +void context_gpio_save(void); +void context_gpio_restore(void); +void context_gpio_restore_mux(void); +void context_gpio_mux_safe_switch(bool begin); + +void context_gic_dist_disable_unneeded_irqs(void); + +void context_varm_save_common(void); +void context_varm_restore_common(void); + +void context_varm_save_core(void); +void context_varm_restore_core(void); + +void context_save_cpu_registers(void); +void context_restore_cpu_registers(void); + +void context_save_to_sram_and_wfi(bool cleanL2cache); + +void context_clean_l1_cache_all(void); +void context_save_arm_registers(u32 **backup_stack); +void context_restore_arm_registers(u32 **backup_stack); + +void context_save_cp15_registers(u32 **backup_stack); +void context_restore_cp15_registers(u32 **backup_stack); + +void context_save_to_sram_and_wfi_internal(u32 backup_sram_storage, + bool cleanL2cache); + +/* DB specific functions in either context-db8500 or context-db5500 */ +void u8500_context_save_icn(void); +void u8500_context_restore_icn(void); +void u8500_context_init(void); + +void u5500_context_save_icn(void); +void u5500_context_restore_icn(void); +void u5500_context_init(void); + +#else + +static inline void context_varm_save_core(void) {} +static inline void context_save_cpu_registers(void) {} +static inline void context_save_to_sram_and_wfi(bool cleanL2cache) {} +static inline void context_restore_cpu_registers(void) {} +static inline void context_varm_restore_core(void) {} + +#endif + +#endif diff --git a/arch/arm/mach-ux500/include/mach/db5500-regs.h b/arch/arm/mach-ux500/include/mach/db5500-regs.h index 6ad98329410..870c3ff574c 100644 --- a/arch/arm/mach-ux500/include/mach/db5500-regs.h +++ b/arch/arm/mach-ux500/include/mach/db5500-regs.h @@ -34,6 +34,7 @@ #define U5500_ICN_BASE 0xA0040000 #define U5500_B2R2_BASE 0xa0200000 #define U5500_BOOT_ROM_BASE 0x90000000 +#define U5500_ASIC_ID_ADDRESS (U5500_BOOT_ROM_BASE + 0x1FFF4) #define U5500_FSMC_BASE (U5500_PER1_BASE + 0x0000) #define U5500_SDI0_BASE (U5500_PER1_BASE + 0x1000) @@ -61,10 +62,14 @@ #define U5500_SCR_BASE (U5500_PER4_BASE + 0x5000) #define U5500_DMC_BASE (U5500_PER4_BASE + 0x6000) #define U5500_PRCMU_BASE (U5500_PER4_BASE + 0x7000) +#define U5500_PRCMU_TIMER_3_BASE (U5500_PER4_BASE + 0x07338) +#define U5500_PRCMU_TIMER_4_BASE (U5500_PER4_BASE + 0x07450) #define U5500_MSP1_BASE (U5500_PER4_BASE + 0x9000) #define U5500_GPIO2_BASE (U5500_PER4_BASE + 0xA000) #define U5500_CDETECT_BASE (U5500_PER4_BASE + 0xF000) #define U5500_PRCMU_TCDM_BASE (U5500_PER4_BASE + 0x18000) +#define U5500_PRCMU_TCPM_BASE (U5500_PER4_BASE + 0x10000) +#define U5500_TPIU_BASE (U5500_PER4_BASE + 0x50000) #define U5500_SPI0_BASE (U5500_PER5_BASE + 0x0000) #define U5500_SPI1_BASE (U5500_PER5_BASE + 0x1000) diff --git a/arch/arm/mach-ux500/include/mach/db8500-regs.h b/arch/arm/mach-ux500/include/mach/db8500-regs.h index 049997109cf..80e10f50282 100644 --- a/arch/arm/mach-ux500/include/mach/db8500-regs.h +++ b/arch/arm/mach-ux500/include/mach/db8500-regs.h @@ -22,7 +22,9 @@ #define U8500_ESRAM_DMA_LCPA_OFFSET 0x10000 #define U8500_DMA_LCPA_BASE (U8500_ESRAM_BANK0 + U8500_ESRAM_DMA_LCPA_OFFSET) -#define U8500_DMA_LCPA_BASE_ED (U8500_ESRAM_BANK4 + 0x4000) + +/* This address fulfills the 256k alignment requirement of the lcla base */ +#define U8500_DMA_LCLA_BASE U8500_ESRAM_BANK4 #define U8500_PER3_BASE 0x80000000 #define U8500_STM_BASE 0x80100000 @@ -40,15 +42,14 @@ #define U8500_ASIC_ID_BASE 0x9001D000 #define U8500_PER6_BASE 0xa03c0000 +#define U8500_PER7_BASE 0xa03d0000 #define U8500_PER5_BASE 0xa03e0000 -#define U8500_PER7_BASE_ED 0xa03d0000 #define U8500_SVA_BASE 0xa0100000 #define U8500_SIA_BASE 0xa0200000 #define U8500_SGA_BASE 0xa0300000 #define U8500_MCDE_BASE 0xa0350000 -#define U8500_DMA_BASE_ED 0xa0362000 #define U8500_DMA_BASE 0x801C0000 /* v1 */ #define U8500_SBAG_BASE 0xa0390000 @@ -66,13 +67,6 @@ #define U8500_GPIO2_BASE (U8500_PER2_BASE + 0xE000) #define U8500_GPIO3_BASE (U8500_PER5_BASE + 0x1E000) -/* per7 base addresses */ -#define U8500_CR_BASE_ED (U8500_PER7_BASE_ED + 0x8000) -#define U8500_MTU0_BASE_ED (U8500_PER7_BASE_ED + 0xa000) -#define U8500_MTU1_BASE_ED (U8500_PER7_BASE_ED + 0xb000) -#define U8500_TZPC0_BASE_ED (U8500_PER7_BASE_ED + 0xc000) -#define U8500_CLKRST7_BASE_ED (U8500_PER7_BASE_ED + 0xf000) - #define U8500_UART0_BASE (U8500_PER1_BASE + 0x0000) #define U8500_UART1_BASE (U8500_PER1_BASE + 0x1000) @@ -102,9 +96,10 @@ #define U8500_SCR_BASE (U8500_PER4_BASE + 0x05000) #define U8500_DMC_BASE (U8500_PER4_BASE + 0x06000) #define U8500_PRCMU_BASE (U8500_PER4_BASE + 0x07000) -#define U8500_PRCMU_TCDM_BASE_V1 (U8500_PER4_BASE + 0x0f000) #define U8500_PRCMU_TCDM_BASE (U8500_PER4_BASE + 0x68000) #define U8500_PRCMU_TCPM_BASE (U8500_PER4_BASE + 0x60000) +#define U8500_PRCMU_TIMER_3_BASE (U8500_PER4_BASE + 0x07338) +#define U8500_PRCMU_TIMER_4_BASE (U8500_PER4_BASE + 0x07450) /* per3 base addresses */ #define U8500_FSMC_BASE (U8500_PER3_BASE + 0x0000) diff --git a/arch/arm/mach-ux500/include/mach/devices.h b/arch/arm/mach-ux500/include/mach/devices.h index 020b6369a30..27e6b5faaa0 100644 --- a/arch/arm/mach-ux500/include/mach/devices.h +++ b/arch/arm/mach-ux500/include/mach/devices.h @@ -13,11 +13,24 @@ struct amba_device; extern struct platform_device u5500_gpio_devs[]; extern struct platform_device u8500_gpio_devs[]; +extern struct platform_device u8500_mcde_device; +extern struct platform_device u5500_mcde_device; +extern struct platform_device u8500_shrm_device; +extern struct platform_device u8500_b2r2_device; +extern struct platform_device u5500_b2r2_device; +extern struct platform_device u8500_trace_modem; +extern struct platform_device ux500_hwmem_device; +extern struct platform_device u8500_stm_device; extern struct amba_device ux500_pl031_device; - -extern struct platform_device u8500_dma40_device; +extern struct platform_device ux500_hash1_device; +extern struct platform_device ux500_cryp1_device; +extern struct platform_device mloader_fw_device; +extern struct platform_device u5500_thsens_device; +extern struct platform_device u8500_thsens_device; extern struct platform_device ux500_ske_keypad_device; - -void dma40_u8500ed_fixup(void); +extern struct platform_device u8500_wdt_device; +extern struct platform_device u8500_hsi_device; +extern struct platform_device ux500_mmio_device; +extern struct platform_device u5500_mmio_device; #endif diff --git a/arch/arm/mach-ux500/include/mach/gpio.h b/arch/arm/mach-ux500/include/mach/gpio.h index 3c4cd31ad9f..58e2a9a7043 100644 --- a/arch/arm/mach-ux500/include/mach/gpio.h +++ b/arch/arm/mach-ux500/include/mach/gpio.h @@ -5,8 +5,19 @@ * 288 (#267 is the highest one actually hooked up) onchip GPIOs, plus enough * room for a couple of GPIO expanders. */ -#define ARCH_NR_GPIOS 350 +#define ARCH_NR_GPIOS 355 +#define NOMADIK_NR_GPIO 288 -#include <plat/gpio.h> +#include <asm-generic/gpio.h> + +/* Invoke gpiolibs gpio_chip abstraction */ +#define gpio_get_value __gpio_get_value +#define gpio_set_value __gpio_set_value +#define gpio_cansleep __gpio_cansleep +#define gpio_to_irq __gpio_to_irq + +#define MOP500_EGPIO(x) (NOMADIK_NR_GPIO + (x)) +#define MOP500_EGPIO_END MOP500_EGPIO(24) +#define AB8500_GPIO_BASE MOP500_EGPIO_END #endif /* __ASM_ARCH_GPIO_H */ diff --git a/arch/arm/mach-ux500/include/mach/hardware.h b/arch/arm/mach-ux500/include/mach/hardware.h index 470ac52663d..5983d53c3c9 100644 --- a/arch/arm/mach-ux500/include/mach/hardware.h +++ b/arch/arm/mach-ux500/include/mach/hardware.h @@ -10,25 +10,56 @@ #ifndef __MACH_HARDWARE_H #define __MACH_HARDWARE_H -/* macros to get at IO space when running virtually +/* + * Macros to get at IO space when running virtually * We dont map all the peripherals, let ioremap do * this for us. We map only very basic peripherals here. */ #define U8500_IO_VIRTUAL 0xf0000000 #define U8500_IO_PHYSICAL 0xa0000000 -/* this macro is used in assembly, so no cast */ +/* This macro is used in assembly, so no cast */ #define IO_ADDRESS(x) \ (((x) & 0x0fffffff) + (((x) >> 4) & 0x0f000000) + U8500_IO_VIRTUAL) /* typesafe io address */ #define __io_address(n) __io(IO_ADDRESS(n)) -/* used by some plat-nomadik code */ +/* Used by some plat-nomadik code */ #define io_p2v(n) __io_address(n) #include <mach/db8500-regs.h> #include <mach/db5500-regs.h> +/* + * FIFO offsets for IPs + */ +#define MSP_TX_RX_REG_OFFSET 0 +#define SSP_TX_RX_REG_OFFSET 0x8 +#define SPI_TX_RX_REG_OFFSET 0x8 +#define SD_MMC_TX_RX_REG_OFFSET 0x80 +#define CRYP1_RX_REG_OFFSET 0x10 +#define CRYP1_TX_REG_OFFSET 0x8 +#define HASH1_TX_REG_OFFSET 0x4 + +/* MSP related board specific declaration************************/ + +#define MSP_DATA_DELAY MSP_DELAY_0 +#define MSP_TX_CLOCK_EDGE MSP_FALLING_EDGE +#define MSP_RX_CLOCK_EDGE MSP_FALLING_EDGE + +#define MSP_0_CONTROLLER 1 +#define MSP_1_CONTROLLER 2 +#define MSP_2_CONTROLLER 3 +#define MSP_3_CONTROLLER 4 + +#define SSP_0_CONTROLLER 4 +#define SSP_1_CONTROLLER 5 + +#define SPI023_0_CONTROLLER 6 +#define SPI023_1_CONTROLLER 7 +#define SPI023_2_CONTROLLER 8 +#define SPI023_3_CONTROLLER 9 + #ifndef __ASSEMBLY__ #include <mach/id.h> @@ -36,6 +67,14 @@ extern void __iomem *_PRCMU_BASE; #define ARRAY_AND_SIZE(x) (x), ARRAY_SIZE(x) + +#ifdef CONFIG_UX500_SOC_DB5500 +bool cpu_is_u5500v1(void); +bool cpu_is_u5500v2(void); +#else +static inline bool cpu_is_u5500v1(void) { return false; } +static inline bool cpu_is_u5500v2(void) { return false; } #endif +#endif /* __ASSEMBLY__ */ #endif /* __MACH_HARDWARE_H */ diff --git a/arch/arm/mach-ux500/include/mach/hcl_defs.h b/arch/arm/mach-ux500/include/mach/hcl_defs.h new file mode 100644 index 00000000000..efd37608cb3 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/hcl_defs.h @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2009 ST-Ericsson SA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef _HCL_DEFS_H +#define _HCL_DEFS_H +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +//#include "platform_os.h" + +/*----------------------------------------------------------------------------- + * Type definition + *---------------------------------------------------------------------------*/ +typedef unsigned char t_uint8; +typedef signed char t_sint8; +typedef unsigned short t_uint16; +typedef signed short t_sint16; +typedef unsigned long t_uint32; +typedef signed long t_sint32; + +typedef unsigned int t_bitfield; + + + +#if !defined(FALSE) && !defined(TRUE) +typedef int t_bool; +#define FALSE 0 +#define TRUE 1 +#endif + +/* + * Definition of the different kind of addresses manipulated into a system with MMU + * (handle physical AND logical addresses) + */ + + +typedef t_uint32 t_physical_address; +typedef t_uint32 t_logical_address; + + + +/* + * Global frequency enumuration + * Added to avoid frequency conversion function which is required to convert one HCL + * frequency enumuration values to another HCL frequency enumuration values. + */ + +/*typedef enum { + HCL_FREQ_NOT_SUPPORTED=-1, + HCL_FREQ_8KHZ , + HCL_FREQ_11_25KHZ, + HCL_FREQ_12KHZ, + HCL_FREQ_16KHZ, + HCL_FREQ_22_05KHZ, + HCL_FREQ_22_5KHZ, + HCL_FREQ_24KHZ, + HCL_FREQ_32KHZ, + HCL_FREQ_44KHZ, + HCL_FREQ_44_1KHZ, + HCL_FREQ_48KHZ, + HCL_FREQ_64KHZ, + HCL_FREQ_88KHZ, + HCL_FREQ_88_2KHZ, + HCL_FREQ_96KHZ, + HCL_FREQ_128KHZ, + HCL_FREQ_176_4KHZ, + HCL_FREQ_192KHZ, + + HCL_FREQ_1MHZ, + HCL_FREQ_2MHZ, + HCL_FREQ_3MHZ, + HCL_FREQ_4MHZ, + HCL_FREQ_5MHZ, + HCL_FREQ_6MHZ, + HCL_FREQ_8MHZ, + HCL_FREQ_11MHZ, + HCL_FREQ_12MHZ, + HCL_FREQ_16MHZ, + HCL_FREQ_22MHZ, + HCL_FREQ_24MHZ, + HCL_FREQ_48MHZ +} t_frequency; + +*/ + +typedef struct { + t_physical_address physical; + t_logical_address logical; +} t_system_address; + + +/* + * Define a type used to manipulate size of various buffers + */ +typedef t_uint32 t_size; + +typedef struct { + t_bitfield minor:8; + t_bitfield major:8; + t_bitfield version:16; +} t_version; + + + + +/*----------------------------------------------------------------------------- + * Keyword definition + *---------------------------------------------------------------------------*/ +#define PUBLIC /* Extern by default */ +#define PRIVATE static + +#ifndef NULL +#define NULL (0) +#endif /* ndef NULL */ + + +/*----------------------------------------------------------------------------- + * Bit setting or clearing + *---------------------------------------------------------------------------*/ +#define HCL_SET_BITS(reg,mask) ((reg) |= (mask)) +#define HCL_CLEAR_BITS(reg,mask) ((reg) &= ~(mask)) +#define HCL_READ_BITS(reg,mask) ((reg) & (mask)) +#define HCL_WRITE_BITS(reg,val,mask) ((reg) = (((reg) & ~(mask)) | ((val) & (mask)))) +#define HCL_READ_REG(reg) (reg) +#define HCL_WRITE_REG(reg,val) ((reg) = (val)) + +/*----------------------------------------------------------------------------- + * field offset extraction from a structure + *---------------------------------------------------------------------------*/ +#define HCL_BITFIELD_OFFSET(typeName, fieldName) (t_uint32)(&(((typeName *)0)->fieldName)) + +/*----------------------------------------------------------------------------- + * Bit mask definition + *---------------------------------------------------------------------------*/ +#define MASK_NULL8 0x00 +#define MASK_NULL16 0x0000 +#define MASK_NULL32 0x00000000 +#define MASK_ALL8 0xFF +#define MASK_ALL16 0xFFFF +#define MASK_ALL32 0xFFFFFFFF + +#define MASK_BIT0 (1UL<<0) +#define MASK_BIT1 (1UL<<1) +#define MASK_BIT2 (1UL<<2) +#define MASK_BIT3 (1UL<<3) +#define MASK_BIT4 (1UL<<4) +#define MASK_BIT5 (1UL<<5) +#define MASK_BIT6 (1UL<<6) +#define MASK_BIT7 (1UL<<7) +#define MASK_BIT8 (1UL<<8) +#define MASK_BIT9 (1UL<<9) +#define MASK_BIT10 (1UL<<10) +#define MASK_BIT11 (1UL<<11) +#define MASK_BIT12 (1UL<<12) +#define MASK_BIT13 (1UL<<13) +#define MASK_BIT14 (1UL<<14) +#define MASK_BIT15 (1UL<<15) +#define MASK_BIT16 (1UL<<16) +#define MASK_BIT17 (1UL<<17) +#define MASK_BIT18 (1UL<<18) +#define MASK_BIT19 (1UL<<19) +#define MASK_BIT20 (1UL<<20) +#define MASK_BIT21 (1UL<<21) +#define MASK_BIT22 (1UL<<22) +#define MASK_BIT23 (1UL<<23) +#define MASK_BIT24 (1UL<<24) +#define MASK_BIT25 (1UL<<25) +#define MASK_BIT26 (1UL<<26) +#define MASK_BIT27 (1UL<<27) +#define MASK_BIT28 (1UL<<28) +#define MASK_BIT29 (1UL<<29) +#define MASK_BIT30 (1UL<<30) +#define MASK_BIT31 (1UL<<31) + +/*----------------------------------------------------------------------------- + * quartet shift definition + *---------------------------------------------------------------------------*/ +#define MASK_QUARTET (0xFUL) +#define SHIFT_QUARTET0 0 +#define SHIFT_QUARTET1 4 +#define SHIFT_QUARTET2 8 +#define SHIFT_QUARTET3 12 +#define SHIFT_QUARTET4 16 +#define SHIFT_QUARTET5 20 +#define SHIFT_QUARTET6 24 +#define SHIFT_QUARTET7 28 +#define MASK_QUARTET0 (MASK_QUARTET << SHIFT_QUARTET0) +#define MASK_QUARTET1 (MASK_QUARTET << SHIFT_QUARTET1) +#define MASK_QUARTET2 (MASK_QUARTET << SHIFT_QUARTET2) +#define MASK_QUARTET3 (MASK_QUARTET << SHIFT_QUARTET3) +#define MASK_QUARTET4 (MASK_QUARTET << SHIFT_QUARTET4) +#define MASK_QUARTET5 (MASK_QUARTET << SHIFT_QUARTET5) +#define MASK_QUARTET6 (MASK_QUARTET << SHIFT_QUARTET6) +#define MASK_QUARTET7 (MASK_QUARTET << SHIFT_QUARTET7) + +/*----------------------------------------------------------------------------- + * Byte shift definition + *---------------------------------------------------------------------------*/ +#define MASK_BYTE (0xFFUL) +#define SHIFT_BYTE0 0 +#define SHIFT_BYTE1 8 +#define SHIFT_BYTE2 16 +#define SHIFT_BYTE3 24 +#define MASK_BYTE0 (MASK_BYTE << SHIFT_BYTE0) +#define MASK_BYTE1 (MASK_BYTE << SHIFT_BYTE1) +#define MASK_BYTE2 (MASK_BYTE << SHIFT_BYTE2) +#define MASK_BYTE3 (MASK_BYTE << SHIFT_BYTE3) + +/*----------------------------------------------------------------------------- + * Halfword shift definition + *---------------------------------------------------------------------------*/ +#define MASK_HALFWORD (0xFFFFUL) +#define SHIFT_HALFWORD0 0 +#define SHIFT_HALFWORD1 16 +#define MASK_HALFWORD0 (MASK_HALFWORD << SHIFT_HALFWORD0) +#define MASK_HALFWORD1 (MASK_HALFWORD << SHIFT_HALFWORD1) + +/*----------------------------------------------------------------------------- + * Global constants definition + *---------------------------------------------------------------------------*/ + #define ONE_KB (1024) + #define ONE_MB (ONE_KB * ONE_KB) + + +/*----------------------------------------------------------------------------- + * Address translation macros declaration + *---------------------------------------------------------------------------*/ + +#define ARM_TO_AHB_ADDR(addr) (addr) +#define AHB_TO_ARM_ADDR(addr) (addr) + +/* For input parameters - would not be changed by the API */ +#define IN +/* For output parameters - would be changes by the API */ +#define OUT +/* For input-output parameters - provides input to the API but would be changed by the API */ +#define INOUT +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _HCL_DEFS_H */ + +/* End of file hcl_defs.h */ + + diff --git a/arch/arm/mach-ux500/include/mach/id.h b/arch/arm/mach-ux500/include/mach/id.h index 02b541a37ee..6f669eebc04 100644 --- a/arch/arm/mach-ux500/include/mach/id.h +++ b/arch/arm/mach-ux500/include/mach/id.h @@ -38,38 +38,36 @@ static inline unsigned int __attribute_const__ dbx500_revision(void) static inline bool __attribute_const__ cpu_is_u8500(void) { - return dbx500_partnumber() == 0x8500; +#ifdef CONFIG_UX500_SOC_DB8500 + /* partnumber 8520 also comes under 8500 */ + return ((dbx500_partnumber() >> 8) & 0xff) == 0x85; +#else + return false; +#endif +} + +static inline bool __attribute_const__ cpu_is_u8520(void) +{ +#ifdef CONFIG_UX500_SOC_DB8500 + return dbx500_partnumber() == 0x8520; +#else + return false; +#endif } static inline bool __attribute_const__ cpu_is_u5500(void) { +#ifdef CONFIG_UX500_SOC_DB5500 return dbx500_partnumber() == 0x5500; +#else + return false; +#endif } /* * 8500 revisions */ -static inline bool __attribute_const__ cpu_is_u8500ed(void) -{ - return cpu_is_u8500() && dbx500_revision() == 0x00; -} - -static inline bool __attribute_const__ cpu_is_u8500v1(void) -{ - return cpu_is_u8500() && (dbx500_revision() & 0xf0) == 0xA0; -} - -static inline bool __attribute_const__ cpu_is_u8500v10(void) -{ - return cpu_is_u8500() && dbx500_revision() == 0xA0; -} - -static inline bool __attribute_const__ cpu_is_u8500v11(void) -{ - return cpu_is_u8500() && dbx500_revision() == 0xA1; -} - static inline bool __attribute_const__ cpu_is_u8500v2(void) { return cpu_is_u8500() && ((dbx500_revision() & 0xf0) == 0xB0); @@ -85,9 +83,14 @@ static inline bool cpu_is_u8500v21(void) return cpu_is_u8500() && (dbx500_revision() == 0xB1); } +static inline bool cpu_is_u8500v22(void) +{ + return cpu_is_u8500() && (dbx500_revision() == 0xB2); +} + static inline bool cpu_is_u8500v20_or_later(void) { - return cpu_is_u8500() && !cpu_is_u8500v10() && !cpu_is_u8500v11(); + return cpu_is_u8500() && ((dbx500_revision() & 0xf0) >= 0xB0); } static inline bool ux500_is_svp(void) diff --git a/arch/arm/mach-ux500/include/mach/irqs-board-mop500.h b/arch/arm/mach-ux500/include/mach/irqs-board-mop500.h index 47969909836..a58aa51fccf 100644 --- a/arch/arm/mach-ux500/include/mach/irqs-board-mop500.h +++ b/arch/arm/mach-ux500/include/mach/irqs-board-mop500.h @@ -43,6 +43,8 @@ #define MOP500_AB8500_VIR_GPIO_IRQ_BASE \ MOP500_STMPE1601_IRQ_END +#define MOP500_AB8500_VIR_GPIO_IRQ(x) \ + (MOP500_AB8500_VIR_GPIO_IRQ_BASE + (x)) #define MOP500_AB8500_VIR_GPIO_IRQ_END \ (MOP500_AB8500_VIR_GPIO_IRQ_BASE + AB8500_VIR_GPIO_NR_IRQS) @@ -57,7 +59,7 @@ */ #if MOP500_IRQ_END > IRQ_BOARD_END #undef IRQ_BOARD_END -#define IRQ_BOARD_END MOP500_IRQ_END +#define IRQ_BOARD_END MOP500_IRQ_END #endif #endif diff --git a/arch/arm/mach-ux500/include/mach/irqs-board-u5500.h b/arch/arm/mach-ux500/include/mach/irqs-board-u5500.h index 29d972c7717..2294a47b3a2 100644 --- a/arch/arm/mach-ux500/include/mach/irqs-board-u5500.h +++ b/arch/arm/mach-ux500/include/mach/irqs-board-u5500.h @@ -7,13 +7,20 @@ #ifndef __MACH_IRQS_BOARD_U5500_H #define __MACH_IRQS_BOARD_U5500_H -#define AB5500_NR_IRQS 5 +#include <linux/mfd/abx500/ab5500.h> + +#define AB5500_NR_IRQS (AB5500_NUM_IRQ_REGS * 8) #define IRQ_AB5500_BASE IRQ_BOARD_START #define IRQ_AB5500_END (IRQ_AB5500_BASE + AB5500_NR_IRQS) #define U5500_IRQ_END IRQ_AB5500_END -#if IRQ_BOARD_END < U5500_IRQ_END +/* + * We may have several boards, but only one will run at a + * time, so the one with most IRQs will bump this ahead, + * but the IRQ_BOARD_START remains the same for either board. + */ +#if U5500_IRQ_END > IRQ_BOARD_END #undef IRQ_BOARD_END #define IRQ_BOARD_END U5500_IRQ_END #endif diff --git a/arch/arm/mach-ux500/include/mach/irqs-db5500.h b/arch/arm/mach-ux500/include/mach/irqs-db5500.h index 77239776a6f..4ea577aefa0 100644 --- a/arch/arm/mach-ux500/include/mach/irqs-db5500.h +++ b/arch/arm/mach-ux500/include/mach/irqs-db5500.h @@ -85,6 +85,36 @@ #ifdef CONFIG_UX500_SOC_DB5500 +/* Virtual interrupts corresponding to the PRCMU wakeups. */ +#define IRQ_DB5500_PRCMU_BASE IRQ_SOC_START + +#define IRQ_DB5500_PRCMU_RTC (IRQ_DB5500_PRCMU_BASE) +#define IRQ_DB5500_PRCMU_RTT0 (IRQ_DB5500_PRCMU_BASE + 1) +#define IRQ_DB5500_PRCMU_RTT1 (IRQ_DB5500_PRCMU_BASE + 2) +#define IRQ_DB5500_PRCMU_CD_IRQ (IRQ_DB5500_PRCMU_BASE + 3) +#define IRQ_DB5500_PRCMU_SRP_TIM (IRQ_DB5500_PRCMU_BASE + 4) +#define IRQ_DB5500_PRCMU_APE_REQ (IRQ_DB5500_PRCMU_BASE + 5) +#define IRQ_DB5500_PRCMU_USB (IRQ_DB5500_PRCMU_BASE + 6) +#define IRQ_DB5500_PRCMU_ABB (IRQ_DB5500_PRCMU_BASE + 7) +#define IRQ_DB5500_PRCMU_ARM (IRQ_DB5500_PRCMU_BASE + 8) +#define IRQ_DB5500_PRCMU_MODEM_SW_RESET_REQ (IRQ_DB5500_PRCMU_BASE + 9) +#define IRQ_DB5500_PRCMU_AC_WAKE_ACK (IRQ_DB5500_PRCMU_BASE + 10) +#define IRQ_DB5500_PRCMU_GPIO0 (IRQ_DB5500_PRCMU_BASE + 11) +#define IRQ_DB5500_PRCMU_GPIO1 (IRQ_DB5500_PRCMU_BASE + 12) +#define IRQ_DB5500_PRCMU_GPIO2 (IRQ_DB5500_PRCMU_BASE + 13) +#define IRQ_DB5500_PRCMU_GPIO3 (IRQ_DB5500_PRCMU_BASE + 14) +#define IRQ_DB5500_PRCMU_GPIO4 (IRQ_DB5500_PRCMU_BASE + 15) +#define IRQ_DB5500_PRCMU_GPIO5 (IRQ_DB5500_PRCMU_BASE + 16) +#define IRQ_DB5500_PRCMU_GPIO6 (IRQ_DB5500_PRCMU_BASE + 17) +#define IRQ_DB5500_PRCMU_GPIO7 (IRQ_DB5500_PRCMU_BASE + 18) +#define IRQ_DB5500_PRCMU_AC_REL_ACK (IRQ_DB5500_PRCMU_BASE + 19) +#define IRQ_DB5500_PRCMU_LOW_POWER_AUDIO (IRQ_DB5500_PRCMU_BASE + 20) +#define IRQ_DB5500_PRCMU_TEMP_SENSOR_LOW (IRQ_DB5500_PRCMU_BASE + 21) +#define IRQ_DB5500_PRCMU_TEMP_SENSOR_HIGH (IRQ_DB5500_PRCMU_BASE + 22) +#define IRQ_DB5500_PRCMU_END (IRQ_DB5500_PRCMU_BASE + 23) + +#define NUM_DB5500_PRCMU_WAKEUPS (IRQ_DB5500_PRCMU_END - IRQ_DB5500_PRCMU_BASE) + /* * After the GPIO ones we reserve a range of IRQ:s in which virtual * IRQ:s representing modem IRQ:s can be allocated diff --git a/arch/arm/mach-ux500/include/mach/irqs.h b/arch/arm/mach-ux500/include/mach/irqs.h index 9db68d264c5..a2876464d43 100644 --- a/arch/arm/mach-ux500/include/mach/irqs.h +++ b/arch/arm/mach-ux500/include/mach/irqs.h @@ -11,9 +11,7 @@ #define ASM_ARCH_IRQS_H #include <mach/hardware.h> - -#define IRQ_LOCALTIMER 29 -#define IRQ_LOCALWDOG 30 +#include <linux/gpio.h> /* Shared Peripheral Interrupt (SHPI) */ #define IRQ_SHPI_START 32 @@ -22,27 +20,34 @@ * MTU0 preserved for now until plat-nomadik is taught not to use it. Don't * add any other IRQs here, use the irqs-dbx500.h files. */ -#define IRQ_MTU0 (IRQ_SHPI_START + 4) +#define IRQ_MTU0 (IRQ_SHPI_START + 4) + +#define IRQ_LOCALTIMER 29 +#define IRQ_LOCALWDOG 30 + +/*********************************************************************/ #define DBX500_NR_INTERNAL_IRQS 160 /* After chip-specific IRQ numbers we have the GPIO ones */ -#define NOMADIK_NR_GPIO 288 #define NOMADIK_GPIO_TO_IRQ(gpio) ((gpio) + DBX500_NR_INTERNAL_IRQS) #define NOMADIK_IRQ_TO_GPIO(irq) ((irq) - DBX500_NR_INTERNAL_IRQS) + +#define GPIO_TO_IRQ NOMADIK_GPIO_TO_IRQ +#define IRQ_TO_GPIO NOMADIK_IRQ_TO_GPIO #define IRQ_GPIO_END NOMADIK_GPIO_TO_IRQ(NOMADIK_NR_GPIO) -#define IRQ_SOC_START IRQ_GPIO_END +#define IRQ_SOC_START IRQ_GPIO_END /* This will be overridden by SoC-specific irq headers */ -#define IRQ_SOC_END IRQ_SOC_START +#define IRQ_SOC_END IRQ_SOC_START + +#define IRQ_BOARD_START IRQ_SOC_END +/* This will be overridden by board-specific irq headers */ +#define IRQ_BOARD_END IRQ_BOARD_START #include <mach/irqs-db5500.h> #include <mach/irqs-db8500.h> -#define IRQ_BOARD_START IRQ_SOC_END -/* This will be overridden by board-specific irq headers */ -#define IRQ_BOARD_END IRQ_BOARD_START - #ifdef CONFIG_MACH_U8500 #include <mach/irqs-board-mop500.h> #endif @@ -51,6 +56,8 @@ #include <mach/irqs-board-u5500.h> #endif +#ifndef NR_IRQS #define NR_IRQS IRQ_BOARD_END +#endif #endif /* ASM_ARCH_IRQS_H */ diff --git a/arch/arm/mach-ux500/include/mach/memory.h b/arch/arm/mach-ux500/include/mach/memory.h index 2ef697a6700..ada8ad0c2d8 100644 --- a/arch/arm/mach-ux500/include/mach/memory.h +++ b/arch/arm/mach-ux500/include/mach/memory.h @@ -15,4 +15,12 @@ #define PLAT_PHYS_OFFSET UL(0x00000000) #define BUS_OFFSET UL(0x00000000) +#ifdef CONFIG_UX500_SOC_DB8500 +/* + * STE NMF CM driver only used on the U8500 allocate using dma_alloc_coherent: + * 8M for SIA and SVA data + 2M for SIA code + 2M for SVA code + */ +#define CONSISTENT_DMA_SIZE SZ_16M +#endif + #endif diff --git a/arch/arm/mach-ux500/include/mach/pm-timer.h b/arch/arm/mach-ux500/include/mach/pm-timer.h new file mode 100644 index 00000000000..ec9e919e70d --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/pm-timer.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + * + * License Terms: GNU General Public License v2 + * + */ + +#ifndef PM_TIMER_H +#define PM_TIMER_H + +#include <linux/ktime.h> + +#ifdef CONFIG_U8500_CPUIDLE_DEBUG +ktime_t u8500_rtc_exit_latency_get(void); +void ux500_rtcrtt_measure_latency(bool enable); +#else +static inline ktime_t u8500_rtc_exit_latency_get(void) +{ + return ktime_set(0, 0); +} +static inline void ux500_rtcrtt_measure_latency(bool enable) { } + +#endif + +void ux500_rtcrtt_off(void); +void ux500_rtcrtt_next(u32 time_us); + +#endif diff --git a/arch/arm/mach-ux500/include/mach/pm.h b/arch/arm/mach-ux500/include/mach/pm.h new file mode 100644 index 00000000000..b9fe3bbc8d7 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/pm.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Rickard Andersson <rickard.andersson@stericsson.com> for + * ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#ifndef PM_COMMON_H +#define PM_COMMON_H + +#ifdef CONFIG_PM +enum prcmu_idle_stat { + SLEEP_OK = 0xf3, + DEEP_SLEEP_OK = 0xf6, + IDLE_OK = 0xf0, + DEEPIDLE_OK = 0xe3, + PRCMU2ARMPENDINGIT_ER = 0x91, + ARMPENDINGIT_ER = 0x93, +}; + +/** + * ux500_pm_gic_decouple() + * + * Decouple GIC from the interrupt bus. + */ +void ux500_pm_gic_decouple(void); + +/** + * ux500_pm_gic_recouple() + * + * Recouple GIC with the interrupt bus. + */ +void ux500_pm_gic_recouple(void); + +/** + * ux500_pm_gic_pending_interrupt() + * + * returns true, if there are pending interrupts. + */ +bool ux500_pm_gic_pending_interrupt(void); + +/** + * ux500_pm_prcmu_pending_interrupt() + * + * returns true, if there are pending interrupts. + */ +bool ux500_pm_prcmu_pending_interrupt(void); + +/** + * ux500_pm_prcmu_set_ioforce() + * + * @enable: Enable/disable + * + * Enable/disable the gpio-ring + */ +void ux500_pm_prcmu_set_ioforce(bool enable); + +/** + * ux500_pm_prcmu_copy_gic_settings() + * + * This function copies all the gic interrupt settings to the prcmu. + * This is needed for the system to catch interrupts in ApIdle + */ +void ux500_pm_prcmu_copy_gic_settings(void); + +/** + * ux500_pm_gpio_save_wake_up_status() + * + * This function is called when the prcmu has woken the ARM + * but before ioforce is disabled. + */ +void ux500_pm_gpio_save_wake_up_status(void); + +/** + * ux500_pm_gpio_read_wake_up_status() + * + * @bank_number: The gpio bank. + * + * Returns the WKS register settings for given bank number. + * The WKS register is cleared when ioforce is released therefore + * this function is needed. + */ +u32 ux500_pm_gpio_read_wake_up_status(unsigned int bank_number); + +/** + * ux500_pm_other_cpu_wfi() + * + * Returns true if the other CPU is in WFI. + */ +bool ux500_pm_other_cpu_wfi(void); + +/** + * ux500_pm_prcmu_idle_stat() + * + * Returns the status of the last prcmu idle/sleep + */ +enum prcmu_idle_stat ux500_pm_prcmu_idle_stat(void); + +struct dev_pm_domain; +extern struct dev_pm_domain ux500_dev_power_domain; +extern struct dev_pm_domain ux500_amba_dev_power_domain; + +#else +u32 ux500_pm_gpio_read_wake_up_status(unsigned int bank_number) +{ + return 0; +} + +/** + * ux500_pm_prcmu_set_ioforce() + * + * @enable: Enable/disable + * + * Enable/disable the gpio-ring + */ +static inline void ux500_pm_prcmu_set_ioforce(bool enable) { } + +#endif + +#endif diff --git a/arch/arm/mach-ux500/include/mach/prcmu-debug.h b/arch/arm/mach-ux500/include/mach/prcmu-debug.h new file mode 100644 index 00000000000..e468543fdef --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/prcmu-debug.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Martin Persson for ST-Ericsson + * Etienne Carriere <etienne.carriere@stericsson.com> for ST-Ericsson + * + */ + +#ifndef PRCMU_DEBUG_H +#define PRCMU_DEBUG_H + +#ifdef CONFIG_DBX500_PRCMU_DEBUG +void prcmu_debug_ape_opp_log(u8 opp); +void prcmu_debug_ddr_opp_log(u8 opp); +void prcmu_debug_arm_opp_log(u8 opp); +#else +static inline void prcmu_debug_ape_opp_log(u8 opp) {} +static inline void prcmu_debug_ddr_opp_log(u8 opp) {} +static inline void prcmu_debug_arm_opp_log(u8 opp) {} +#endif +#endif diff --git a/arch/arm/mach-ux500/include/mach/reboot_reasons.h b/arch/arm/mach-ux500/include/mach/reboot_reasons.h new file mode 100644 index 00000000000..2c21aab58c4 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/reboot_reasons.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Rickard Evertsson <rickard.evertsson@stericsson.com> + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL) version 2 + * + * Use this file to customize your reboot / sw reset reasons. Add, remove or + * modify reasons in reboot_reasons[]. + * The reboot reasons will be saved to a secure location in TCDM memory and + * can be read at bootup by e.g. the bootloader, or at a later stage userspace + * since the code is exposed through sysfs. + */ + +#ifndef _REBOOT_REASONS_H +#define _REBOOT_REASONS_H + +/* + * These defines contains the codes that will be written down to a secure + * location before resetting. These values are exposed through a sysfs + * entry under /sys/socinfo, see mach-ux500/cpu-db8500.c + */ +#define SW_RESET_NO_ARGUMENT 0xBEEF +#define SW_RESET_FACTORY_RESET 0x4242 +#define SW_RESET_CRASH 0xDEAD +#define SW_RESET_NORMAL 0xc001 +#define SW_RESET_CHARGING 0xCAFE +#define SW_RESET_COLDSTART 0x0 +#define SW_RESET_RECOVERY 0x5502 + +/* + * The array reboot_reasons[] is used when you want to map a string to a reboot + * reason code + */ +struct reboot_reason { + const char *reason; + u16 code; +}; + +extern struct reboot_reason reboot_reasons[]; + +extern unsigned int reboot_reasons_size; + +u16 reboot_reason_code(const char *cmd); +const char *reboot_reason_string(u16 code); + +#endif diff --git a/arch/arm/mach-ux500/include/mach/sensors1p.h b/arch/arm/mach-ux500/include/mach/sensors1p.h new file mode 100644 index 00000000000..544e1d8bab5 --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/sensors1p.h @@ -0,0 +1,24 @@ + +/* + * Copyright (C) 2009-2010 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * Header file for 1 pin gpio sensors; + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + * + */ + +#ifndef __ASM_ARCH_SFH7741_H +#define __ASM_ARCH_SFH7741_H + +struct sensor_config { + int pin; + int startup_time; /* in ms */ + char regulator[32]; +}; + +struct sensors1p_config { + struct sensor_config hal; + struct sensor_config proximity; +}; + +#endif diff --git a/arch/arm/mach-ux500/include/mach/setup.h b/arch/arm/mach-ux500/include/mach/setup.h index a7d363fdb4c..cfb37baaa6f 100644 --- a/arch/arm/mach-ux500/include/mach/setup.h +++ b/arch/arm/mach-ux500/include/mach/setup.h @@ -18,6 +18,7 @@ void __init ux500_map_io(void); extern void __init u5500_map_io(void); extern void __init u8500_map_io(void); +extern void __init ux500_init_devices(void); extern void __init u5500_init_devices(void); extern void __init u8500_init_devices(void); @@ -26,6 +27,7 @@ extern void __init ux500_init_irq(void); extern void __init u5500_sdi_init(void); extern void __init db5500_dma_init(void); +extern void __init db8500_dma_init(void); /* We re-use nomadik_timer for this platform */ extern void nmdk_timer_init(void); diff --git a/arch/arm/mach-ux500/ste-dma40-db5500.h b/arch/arm/mach-ux500/include/mach/ste-dma40-db5500.h index cb2110c3285..0ddd4ab9020 100644 --- a/arch/arm/mach-ux500/ste-dma40-db5500.h +++ b/arch/arm/mach-ux500/include/mach/ste-dma40-db5500.h @@ -42,7 +42,9 @@ enum dma_src_dev_type { DB5500_DMA_DEV26_SDMMC2_RX = 26, DB5500_DMA_DEV27_SDMMC3_RX = 27, DB5500_DMA_DEV28_SDMMC4_RX = 28, - /* 29 - 32 not used */ + /* 29, 30 not used */ + DB5500_DMA_DEV31_CRYPTO1_RX = 31, /* v2 */ + /* 32 not used */ DB5500_DMA_DEV33_SDMMC0_RX = 33, DB5500_DMA_DEV34_SDMMC1_RX = 34, DB5500_DMA_DEV35_SDMMC2_RX = 35, @@ -56,7 +58,7 @@ enum dma_src_dev_type { DB5500_DMA_DEV43_USB_OTG_IEP_5_13 = 43, DB5500_DMA_DEV44_USB_OTG_IEP_6_14 = 44, DB5500_DMA_DEV45_USB_OTG_IEP_7_15 = 45, - /* 46 not used */ + DB5500_DMA_DEV46_CRYPTO1_RX = 46, /* v2 */ DB5500_DMA_DEV47_MCDE_RX = 47, DB5500_DMA_DEV48_CRYPTO1_RX = 48, /* 49, 50 not used */ @@ -98,7 +100,9 @@ enum dma_dest_dev_type { DB5500_DMA_DEV26_SDMMC2_TX = 26, DB5500_DMA_DEV27_SDMMC3_TX = 27, DB5500_DMA_DEV28_SDMMC4_TX = 28, - /* 29 - 31 not used */ + /* 29 not used */ + DB5500_DMA_DEV30_HASH1_TX = 30, /* v2 */ + DB5500_DMA_DEV31_CRYPTO1_TX = 31, /* v2 */ DB5500_DMA_DEV32_FSMC_TX = 32, DB5500_DMA_DEV33_SDMMC0_TX = 33, DB5500_DMA_DEV34_SDMMC1_TX = 34, @@ -113,7 +117,7 @@ enum dma_dest_dev_type { DB5500_DMA_DEV43_USB_OTG_OEP_5_13 = 43, DB5500_DMA_DEV44_USB_OTG_OEP_6_14 = 44, DB5500_DMA_DEV45_USB_OTG_OEP_7_15 = 45, - /* 46 not used */ + DB5500_DMA_DEV46_CRYPTO1_TX = 46, /* v2 */ DB5500_DMA_DEV47_STM_TX = 47, DB5500_DMA_DEV48_CRYPTO1_TX = 48, DB5500_DMA_DEV49_CRYPTO1_TX_HASH1_TX = 49, diff --git a/arch/arm/mach-ux500/ste-dma40-db8500.h b/arch/arm/mach-ux500/include/mach/ste-dma40-db8500.h index a616419bea7..65799a75199 100644 --- a/arch/arm/mach-ux500/ste-dma40-db8500.h +++ b/arch/arm/mach-ux500/include/mach/ste-dma40-db8500.h @@ -1,16 +1,19 @@ /* - * arch/arm/mach-ux500/ste_dma40_db8500.h - * DB8500-SoC-specific configuration for DMA40 - * - * Copyright (C) ST-Ericsson 2007-2010 + * Copyright (C) ST-Ericsson SA 2007-2010 + * Author: Per Friden <per.friden@stericsson.com> for ST-Ericsson + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson * License terms: GNU General Public License (GPL) version 2 - * Author: Per Friden <per.friden@stericsson.com> - * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + * + * DB8500-SoC-specific configuration for DMA40 */ #ifndef STE_DMA40_DB8500_H #define STE_DMA40_DB8500_H #define DB8500_DMA_NR_DEV 64 +/* + * All entries with double names are multiplexed + * and can never be used at the same time. + */ enum dma_src_dev_type { DB8500_DMA_DEV0_SPI0_RX = 0, @@ -20,7 +23,7 @@ enum dma_src_dev_type { DB8500_DMA_DEV4_I2C1_RX = 4, DB8500_DMA_DEV5_I2C3_RX = 5, DB8500_DMA_DEV6_I2C2_RX = 6, - DB8500_DMA_DEV7_I2C4_RX = 7, /* Only on V1 and later */ + DB8500_DMA_DEV7_I2C4_RX = 7, DB8500_DMA_DEV8_SSP0_RX = 8, DB8500_DMA_DEV9_SSP1_RX = 9, DB8500_DMA_DEV10_MCDE_RX = 10, @@ -43,8 +46,6 @@ enum dma_src_dev_type { DB8500_DMA_DEV27_SRC_SXA3_RX_TX = 27, DB8500_DMA_DEV28_SD_MM2_RX = 28, DB8500_DMA_DEV29_SD_MM0_RX = 29, - DB8500_DMA_DEV30_MSP1_RX = 30, - /* On DB8500v2, MSP3 RX replaces MSP1 RX */ DB8500_DMA_DEV30_MSP3_RX = 30, DB8500_DMA_DEV31_MSP0_RX_SLIM0_CH0_RX = 31, DB8500_DMA_DEV32_SD_MM1_RX = 32, @@ -82,7 +83,7 @@ enum dma_dest_dev_type { DB8500_DMA_DEV4_I2C1_TX = 4, DB8500_DMA_DEV5_I2C3_TX = 5, DB8500_DMA_DEV6_I2C2_TX = 6, - DB8500_DMA_DEV7_I2C4_TX = 7, /* Only on V1 and later */ + DB8500_DMA_DEV7_I2C4_TX = 7, DB8500_DMA_DEV8_SSP0_TX = 8, DB8500_DMA_DEV9_SSP1_TX = 9, /* 10 is not used*/ diff --git a/arch/arm/mach-ux500/include/mach/suspend.h b/arch/arm/mach-ux500/include/mach/suspend.h new file mode 100644 index 00000000000..5a8df72be2e --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/suspend.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License terms: GNU General Public License (GPL) version 2 + */ +#ifndef __MACH_SUSPEND_H +#define __MACH_SUSPEND_H + +#ifdef CONFIG_UX500_SUSPEND +void suspend_block_sleep(void); +void suspend_unblock_sleep(void); +void suspend_set_pins_force_fn(void (*force)(void), void (*force_mux)(void)); +#else +static inline void suspend_block_sleep(void) { } +static inline void suspend_unblock_sleep(void) { } +static inline void suspend_set_pins_force_fn(void (*force)(void), + void (*force_mux)(void)) { } +#endif + +#endif /* __MACH_SUSPEND_H */ diff --git a/arch/arm/mach-ux500/include/mach/system.h b/arch/arm/mach-ux500/include/mach/system.h index c0cd8006f1a..42bc8c72a79 100644 --- a/arch/arm/mach-ux500/include/mach/system.h +++ b/arch/arm/mach-ux500/include/mach/system.h @@ -8,6 +8,9 @@ #ifndef __ASM_ARCH_SYSTEM_H #define __ASM_ARCH_SYSTEM_H +#include <linux/mfd/dbx500-prcmu.h> +#include <mach/reboot_reasons.h> + static inline void arch_idle(void) { /* @@ -19,7 +22,10 @@ static inline void arch_idle(void) static inline void arch_reset(char mode, const char *cmd) { - /* yet to be implemented - TODO */ +#ifdef CONFIG_UX500_SOC_DB8500 + /* Call the PRCMU reset API (w/o reset reason code) */ + prcmu_system_reset(SW_RESET_NO_ARGUMENT); +#endif } #endif diff --git a/arch/arm/mach-ux500/include/mach/timex.h b/arch/arm/mach-ux500/include/mach/timex.h index d0942c17401..0ba497bd9d7 100644 --- a/arch/arm/mach-ux500/include/mach/timex.h +++ b/arch/arm/mach-ux500/include/mach/timex.h @@ -2,5 +2,6 @@ #define __ASM_ARCH_TIMEX_H #define CLOCK_TICK_RATE 110000000 +#define ARCH_HAS_READ_CURRENT_TIMER #endif diff --git a/arch/arm/mach-ux500/include/mach/uncompress.h b/arch/arm/mach-ux500/include/mach/uncompress.h index 7dd08074c37..894f7d23f06 100644 --- a/arch/arm/mach-ux500/include/mach/uncompress.h +++ b/arch/arm/mach-ux500/include/mach/uncompress.h @@ -52,8 +52,6 @@ static inline void arch_decomp_setup(void) { /* Check in run time if we run on an U8500 or U5500 */ if (machine_is_u8500() || - machine_is_svp8500v1() || - machine_is_svp8500v2() || machine_is_hrefv60() || machine_is_snowball()) ux500_uart_base = U8500_UART2_BASE; diff --git a/arch/arm/mach-ux500/include/mach/vmalloc.h b/arch/arm/mach-ux500/include/mach/vmalloc.h index a4945cb4117..e0c6c408552 100644 --- a/arch/arm/mach-ux500/include/mach/vmalloc.h +++ b/arch/arm/mach-ux500/include/mach/vmalloc.h @@ -15,4 +15,4 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#define VMALLOC_END 0xf0000000UL +#define VMALLOC_END 0xf7800000UL diff --git a/arch/arm/mach-ux500/l2x0-prefetch.c b/arch/arm/mach-ux500/l2x0-prefetch.c new file mode 100644 index 00000000000..48a4495533f --- /dev/null +++ b/arch/arm/mach-ux500/l2x0-prefetch.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com> + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/tee.h> +#include <linux/io.h> +#include <mach/hardware.h> +#include <asm/hardware/cache-l2x0.h> + +static struct tee_session session; +static struct tee_context context; +static void __iomem *l2x0_base; + +#define L2X0_PREFETCH_CTRL_REG (0x00000F60) +#define L2X0_PREFETCH_CTRL_BIT_DATA_EN (1 << 28) +#define L2X0_PREFETCH_CTRL_BIT_INST_EN (1 << 29) + +#define L2X0_UUID_TEE_TA_START_LOW 0xBC765EDE +#define L2X0_UUID_TEE_TA_START_MID 0x6724 +#define L2X0_UUID_TEE_TA_START_HIGH 0x11DF +#define L2X0_UUID_TEE_TA_START_CLOCKSEQ \ + {0x8E, 0x12, 0xEC, 0xDB, 0xDF, 0xD7, 0x20, 0x85} + +static void prefetch_enable(void) +{ + struct tee_operation operation; + u32 data; + int err; + int origin_err; + + data = readl(l2x0_base + L2X0_PREFETCH_CTRL_REG); + + pr_debug("l2x0-prefetch: %s start, preftect_ctrl=0x%08x\n", __func__, + data); + if (!(data & L2X0_PREFETCH_CTRL_BIT_INST_EN) || + !(data & L2X0_PREFETCH_CTRL_BIT_DATA_EN)) { + + data |= (L2X0_PREFETCH_CTRL_BIT_INST_EN | + L2X0_PREFETCH_CTRL_BIT_DATA_EN); + + operation.shm[0].buffer = &data; + operation.shm[0].size = sizeof(data); + operation.shm[0].flags = TEEC_MEM_INPUT; + operation.flags = TEEC_MEMREF_0_USED; + + err = teec_invoke_command(&session, + TEE_STA_SET_L2CC_PREFETCH_CTRL_REGISTER, + &operation, &origin_err); + if (err) + pr_err("l2x0-prefetch: prefetch enable failed, err=%d", + err); + } + pr_debug("l2x0-prefetch: %s end, prefetch_ctrl=0x%08x\n", __func__, + readl(l2x0_base + L2X0_PREFETCH_CTRL_REG)); +} + +static void prefetch_disable(void) +{ + struct tee_operation operation; + u32 data; + int err; + int origin_err; + + data = readl(l2x0_base + L2X0_PREFETCH_CTRL_REG); + + pr_debug("l2x0-prefetch: %s start, preftect_ctrl=0x%08x\n", __func__, + data); + if (data & (L2X0_PREFETCH_CTRL_BIT_INST_EN | + L2X0_PREFETCH_CTRL_BIT_DATA_EN)) { + + data &= ~(L2X0_PREFETCH_CTRL_BIT_INST_EN | + L2X0_PREFETCH_CTRL_BIT_DATA_EN); + + operation.shm[0].buffer = &data; + operation.shm[0].size = sizeof(data); + operation.shm[0].flags = TEEC_MEM_INPUT; + operation.flags = TEEC_MEMREF_0_USED; + + err = teec_invoke_command(&session, + TEE_STA_SET_L2CC_PREFETCH_CTRL_REGISTER, + &operation, &origin_err); + if (err) + pr_err("l2x0-prefetch: prefetch disable failed, err=%d", + err); + } + pr_debug("l2x0-prefetch: %s end, prefetch_ctrl=0x%08x\n", __func__, + readl(l2x0_base + L2X0_PREFETCH_CTRL_REG)); +} + +static int __init prefetch_ctrl_init(void) +{ + int err; + int origin_err; + /* Selects trustzone application needed for the job. */ + struct tee_uuid static_uuid = { + L2X0_UUID_TEE_TA_START_LOW, + L2X0_UUID_TEE_TA_START_MID, + L2X0_UUID_TEE_TA_START_HIGH, + L2X0_UUID_TEE_TA_START_CLOCKSEQ, + }; + + /* Get PL310 base address. It will be used as readonly. */ + if (cpu_is_u5500()) + l2x0_base = __io_address(U5500_L2CC_BASE); + else if (cpu_is_u8500()) + l2x0_base = __io_address(U8500_L2CC_BASE); + else + ux500_unknown_soc(); + + err = teec_initialize_context(NULL, &context); + if (err) { + pr_err("l2x0-prefetch: unable to initialize tee context," + " err = %d\n", err); + err = -EINVAL; + goto error0; + } + + err = teec_open_session(&context, &session, &static_uuid, + TEEC_LOGIN_PUBLIC, NULL, NULL, &origin_err); + if (err) { + pr_err("l2x0-prefetch: unable to open tee session," + " tee error = %d, origin error = %d\n", + err, origin_err); + err = -EINVAL; + goto error1; + } + + outer_cache.prefetch_enable = prefetch_enable; + outer_cache.prefetch_disable = prefetch_disable; + + pr_info("l2x0-prefetch: initialized.\n"); + + return 0; + +error1: + (void)teec_finalize_context(&context); +error0: + return err; +} + +static void __exit prefetch_ctrl_exit(void) +{ + outer_cache.prefetch_enable = NULL; + outer_cache.prefetch_disable = NULL; + + (void)teec_close_session(&session); + (void)teec_finalize_context(&context); +} + +/* Wait for TEE driver to be initialized. */ +late_initcall(prefetch_ctrl_init); +module_exit(prefetch_ctrl_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("PL310 prefetch control"); diff --git a/arch/arm/mach-ux500/mbox-db5500.c b/arch/arm/mach-ux500/mbox-db5500.c deleted file mode 100644 index 2b2d51caf9d..00000000000 --- a/arch/arm/mach-ux500/mbox-db5500.c +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Copyright (C) ST-Ericsson SA 2010 - * Author: Stefan Nilsson <stefan.xk.nilsson@stericsson.com> for ST-Ericsson. - * Author: Martin Persson <martin.persson@stericsson.com> for ST-Ericsson. - * License terms: GNU General Public License (GPL), version 2. - */ - -/* - * Mailbox nomenclature: - * - * APE MODEM - * mbox pairX - * .......................... - * . . - * . peer . - * . send ---- . - * . --> | | . - * . | | . - * . ---- . - * . . - * . local . - * . rec ---- . - * . | | <-- . - * . | | . - * . ---- . - * ......................... - */ - -#include <linux/init.h> -#include <linux/module.h> -#include <linux/device.h> -#include <linux/interrupt.h> -#include <linux/spinlock.h> -#include <linux/errno.h> -#include <linux/io.h> -#include <linux/irq.h> -#include <linux/platform_device.h> -#include <linux/debugfs.h> -#include <linux/seq_file.h> -#include <linux/completion.h> -#include <mach/mbox-db5500.h> - -#define MBOX_NAME "mbox" - -#define MBOX_FIFO_DATA 0x000 -#define MBOX_FIFO_ADD 0x004 -#define MBOX_FIFO_REMOVE 0x008 -#define MBOX_FIFO_THRES_FREE 0x00C -#define MBOX_FIFO_THRES_OCCUP 0x010 -#define MBOX_FIFO_STATUS 0x014 - -#define MBOX_DISABLE_IRQ 0x4 -#define MBOX_ENABLE_IRQ 0x0 -#define MBOX_LATCH 1 - -/* Global list of all mailboxes */ -static struct list_head mboxs = LIST_HEAD_INIT(mboxs); - -static struct mbox *get_mbox_with_id(u8 id) -{ - u8 i; - struct list_head *pos = &mboxs; - for (i = 0; i <= id; i++) - pos = pos->next; - - return (struct mbox *) list_entry(pos, struct mbox, list); -} - -int mbox_send(struct mbox *mbox, u32 mbox_msg, bool block) -{ - int res = 0; - - spin_lock(&mbox->lock); - - dev_dbg(&(mbox->pdev->dev), - "About to buffer 0x%X to mailbox 0x%X." - " ri = %d, wi = %d\n", - mbox_msg, (u32)mbox, mbox->read_index, - mbox->write_index); - - /* Check if write buffer is full */ - while (((mbox->write_index + 1) % MBOX_BUF_SIZE) == mbox->read_index) { - if (!block) { - dev_dbg(&(mbox->pdev->dev), - "Buffer full in non-blocking call! " - "Returning -ENOMEM!\n"); - res = -ENOMEM; - goto exit; - } - spin_unlock(&mbox->lock); - dev_dbg(&(mbox->pdev->dev), - "Buffer full in blocking call! Sleeping...\n"); - mbox->client_blocked = 1; - wait_for_completion(&mbox->buffer_available); - dev_dbg(&(mbox->pdev->dev), - "Blocking send was woken up! Trying again...\n"); - spin_lock(&mbox->lock); - } - - mbox->buffer[mbox->write_index] = mbox_msg; - mbox->write_index = (mbox->write_index + 1) % MBOX_BUF_SIZE; - - /* - * Indicate that we want an IRQ as soon as there is a slot - * in the FIFO - */ - writel(MBOX_ENABLE_IRQ, mbox->virtbase_peer + MBOX_FIFO_THRES_FREE); - -exit: - spin_unlock(&mbox->lock); - return res; -} -EXPORT_SYMBOL(mbox_send); - -#if defined(CONFIG_DEBUG_FS) -/* - * Expected input: <value> <nbr sends> - * Example: "echo 0xdeadbeef 4 > mbox-node" sends 0xdeadbeef 4 times - */ -static ssize_t mbox_write_fifo(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - unsigned long mbox_mess; - unsigned long nbr_sends; - unsigned long i; - char int_buf[16]; - char *token; - char *val; - - struct mbox *mbox = (struct mbox *) dev->platform_data; - - strncpy((char *) &int_buf, buf, sizeof(int_buf)); - token = (char *) &int_buf; - - /* Parse message */ - val = strsep(&token, " "); - if ((val == NULL) || (strict_strtoul(val, 16, &mbox_mess) != 0)) - mbox_mess = 0xDEADBEEF; - - val = strsep(&token, " "); - if ((val == NULL) || (strict_strtoul(val, 10, &nbr_sends) != 0)) - nbr_sends = 1; - - dev_dbg(dev, "Will write 0x%lX %ld times using data struct at 0x%X\n", - mbox_mess, nbr_sends, (u32) mbox); - - for (i = 0; i < nbr_sends; i++) - mbox_send(mbox, mbox_mess, true); - - return count; -} - -static ssize_t mbox_read_fifo(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - int mbox_value; - struct mbox *mbox = (struct mbox *) dev->platform_data; - - if ((readl(mbox->virtbase_local + MBOX_FIFO_STATUS) & 0x7) <= 0) - return sprintf(buf, "Mailbox is empty\n"); - - mbox_value = readl(mbox->virtbase_local + MBOX_FIFO_DATA); - writel(MBOX_LATCH, (mbox->virtbase_local + MBOX_FIFO_REMOVE)); - - return sprintf(buf, "0x%X\n", mbox_value); -} - -static DEVICE_ATTR(fifo, S_IWUGO | S_IRUGO, mbox_read_fifo, mbox_write_fifo); - -static int mbox_show(struct seq_file *s, void *data) -{ - struct list_head *pos; - u8 mbox_index = 0; - - list_for_each(pos, &mboxs) { - struct mbox *m = - (struct mbox *) list_entry(pos, struct mbox, list); - if (m == NULL) { - seq_printf(s, - "Unable to retrieve mailbox %d\n", - mbox_index); - continue; - } - - spin_lock(&m->lock); - if ((m->virtbase_peer == NULL) || (m->virtbase_local == NULL)) { - seq_printf(s, "MAILBOX %d not setup or corrupt\n", - mbox_index); - spin_unlock(&m->lock); - continue; - } - - seq_printf(s, - "===========================\n" - " MAILBOX %d\n" - " PEER MAILBOX DUMP\n" - "---------------------------\n" - "FIFO: 0x%X (%d)\n" - "Free Threshold: 0x%.2X (%d)\n" - "Occupied Threshold: 0x%.2X (%d)\n" - "Status: 0x%.2X (%d)\n" - " Free spaces (ot): %d (%d)\n" - " Occup spaces (ot): %d (%d)\n" - "===========================\n" - " LOCAL MAILBOX DUMP\n" - "---------------------------\n" - "FIFO: 0x%.X (%d)\n" - "Free Threshold: 0x%.2X (%d)\n" - "Occupied Threshold: 0x%.2X (%d)\n" - "Status: 0x%.2X (%d)\n" - " Free spaces (ot): %d (%d)\n" - " Occup spaces (ot): %d (%d)\n" - "===========================\n" - "write_index: %d\n" - "read_index : %d\n" - "===========================\n" - "\n", - mbox_index, - readl(m->virtbase_peer + MBOX_FIFO_DATA), - readl(m->virtbase_peer + MBOX_FIFO_DATA), - readl(m->virtbase_peer + MBOX_FIFO_THRES_FREE), - readl(m->virtbase_peer + MBOX_FIFO_THRES_FREE), - readl(m->virtbase_peer + MBOX_FIFO_THRES_OCCUP), - readl(m->virtbase_peer + MBOX_FIFO_THRES_OCCUP), - readl(m->virtbase_peer + MBOX_FIFO_STATUS), - readl(m->virtbase_peer + MBOX_FIFO_STATUS), - (readl(m->virtbase_peer + MBOX_FIFO_STATUS) >> 4) & 0x7, - (readl(m->virtbase_peer + MBOX_FIFO_STATUS) >> 7) & 0x1, - (readl(m->virtbase_peer + MBOX_FIFO_STATUS) >> 0) & 0x7, - (readl(m->virtbase_peer + MBOX_FIFO_STATUS) >> 3) & 0x1, - readl(m->virtbase_local + MBOX_FIFO_DATA), - readl(m->virtbase_local + MBOX_FIFO_DATA), - readl(m->virtbase_local + MBOX_FIFO_THRES_FREE), - readl(m->virtbase_local + MBOX_FIFO_THRES_FREE), - readl(m->virtbase_local + MBOX_FIFO_THRES_OCCUP), - readl(m->virtbase_local + MBOX_FIFO_THRES_OCCUP), - readl(m->virtbase_local + MBOX_FIFO_STATUS), - readl(m->virtbase_local + MBOX_FIFO_STATUS), - (readl(m->virtbase_local + MBOX_FIFO_STATUS) >> 4) & 0x7, - (readl(m->virtbase_local + MBOX_FIFO_STATUS) >> 7) & 0x1, - (readl(m->virtbase_local + MBOX_FIFO_STATUS) >> 0) & 0x7, - (readl(m->virtbase_local + MBOX_FIFO_STATUS) >> 3) & 0x1, - m->write_index, m->read_index); - mbox_index++; - spin_unlock(&m->lock); - } - - return 0; -} - -static int mbox_open(struct inode *inode, struct file *file) -{ - return single_open(file, mbox_show, NULL); -} - -static const struct file_operations mbox_operations = { - .owner = THIS_MODULE, - .open = mbox_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; -#endif - -static irqreturn_t mbox_irq(int irq, void *arg) -{ - u32 mbox_value; - int nbr_occup; - int nbr_free; - struct mbox *mbox = (struct mbox *) arg; - - spin_lock(&mbox->lock); - - dev_dbg(&(mbox->pdev->dev), - "mbox IRQ [%d] received. ri = %d, wi = %d\n", - irq, mbox->read_index, mbox->write_index); - - /* - * Check if we have any outgoing messages, and if there is space for - * them in the FIFO. - */ - if (mbox->read_index != mbox->write_index) { - /* - * Check by reading FREE for LOCAL since that indicates - * OCCUP for PEER - */ - nbr_free = (readl(mbox->virtbase_local + MBOX_FIFO_STATUS) - >> 4) & 0x7; - dev_dbg(&(mbox->pdev->dev), - "Status indicates %d empty spaces in the FIFO!\n", - nbr_free); - - while ((nbr_free > 0) && - (mbox->read_index != mbox->write_index)) { - /* Write the message and latch it into the FIFO */ - writel(mbox->buffer[mbox->read_index], - (mbox->virtbase_peer + MBOX_FIFO_DATA)); - writel(MBOX_LATCH, - (mbox->virtbase_peer + MBOX_FIFO_ADD)); - dev_dbg(&(mbox->pdev->dev), - "Wrote message 0x%X to addr 0x%X\n", - mbox->buffer[mbox->read_index], - (u32) (mbox->virtbase_peer + MBOX_FIFO_DATA)); - - nbr_free--; - mbox->read_index = - (mbox->read_index + 1) % MBOX_BUF_SIZE; - } - - /* - * Check if we still want IRQ:s when there is free - * space to send - */ - if (mbox->read_index != mbox->write_index) { - dev_dbg(&(mbox->pdev->dev), - "Still have messages to send, but FIFO full. " - "Request IRQ again!\n"); - writel(MBOX_ENABLE_IRQ, - mbox->virtbase_peer + MBOX_FIFO_THRES_FREE); - } else { - dev_dbg(&(mbox->pdev->dev), - "No more messages to send. " - "Do not request IRQ again!\n"); - writel(MBOX_DISABLE_IRQ, - mbox->virtbase_peer + MBOX_FIFO_THRES_FREE); - } - - /* - * Check if we can signal any blocked clients that it is OK to - * start buffering again - */ - if (mbox->client_blocked && - (((mbox->write_index + 1) % MBOX_BUF_SIZE) - != mbox->read_index)) { - dev_dbg(&(mbox->pdev->dev), - "Waking up blocked client\n"); - complete(&mbox->buffer_available); - mbox->client_blocked = 0; - } - } - - /* Check if we have any incoming messages */ - nbr_occup = readl(mbox->virtbase_local + MBOX_FIFO_STATUS) & 0x7; - if (nbr_occup == 0) - goto exit; - - if (mbox->cb == NULL) { - dev_dbg(&(mbox->pdev->dev), "No receive callback registered, " - "leaving %d incoming messages in fifo!\n", nbr_occup); - goto exit; - } - - /* Read and acknowledge the message */ - mbox_value = readl(mbox->virtbase_local + MBOX_FIFO_DATA); - writel(MBOX_LATCH, (mbox->virtbase_local + MBOX_FIFO_REMOVE)); - - /* Notify consumer of new mailbox message */ - dev_dbg(&(mbox->pdev->dev), "Calling callback for message 0x%X!\n", - mbox_value); - mbox->cb(mbox_value, mbox->client_data); - -exit: - dev_dbg(&(mbox->pdev->dev), "Exit mbox IRQ. ri = %d, wi = %d\n", - mbox->read_index, mbox->write_index); - spin_unlock(&mbox->lock); - - return IRQ_HANDLED; -} - -/* Setup is executed once for each mbox pair */ -struct mbox *mbox_setup(u8 mbox_id, mbox_recv_cb_t *mbox_cb, void *priv) -{ - struct resource *resource; - int irq; - int res; - struct mbox *mbox; - - mbox = get_mbox_with_id(mbox_id); - if (mbox == NULL) { - dev_err(&(mbox->pdev->dev), "Incorrect mailbox id: %d!\n", - mbox_id); - goto exit; - } - - /* - * Check if mailbox has been allocated to someone else, - * otherwise allocate it - */ - if (mbox->allocated) { - dev_err(&(mbox->pdev->dev), "Mailbox number %d is busy!\n", - mbox_id); - mbox = NULL; - goto exit; - } - mbox->allocated = true; - - dev_dbg(&(mbox->pdev->dev), "Initiating mailbox number %d: 0x%X...\n", - mbox_id, (u32)mbox); - - mbox->client_data = priv; - mbox->cb = mbox_cb; - - /* Get addr for peer mailbox and ioremap it */ - resource = platform_get_resource_byname(mbox->pdev, - IORESOURCE_MEM, - "mbox_peer"); - if (resource == NULL) { - dev_err(&(mbox->pdev->dev), - "Unable to retrieve mbox peer resource\n"); - mbox = NULL; - goto exit; - } - dev_dbg(&(mbox->pdev->dev), - "Resource name: %s start: 0x%X, end: 0x%X\n", - resource->name, resource->start, resource->end); - mbox->virtbase_peer = ioremap(resource->start, resource_size(resource)); - if (!mbox->virtbase_peer) { - dev_err(&(mbox->pdev->dev), "Unable to ioremap peer mbox\n"); - mbox = NULL; - goto exit; - } - dev_dbg(&(mbox->pdev->dev), - "ioremapped peer physical: (0x%X-0x%X) to virtual: 0x%X\n", - resource->start, resource->end, (u32) mbox->virtbase_peer); - - /* Get addr for local mailbox and ioremap it */ - resource = platform_get_resource_byname(mbox->pdev, - IORESOURCE_MEM, - "mbox_local"); - if (resource == NULL) { - dev_err(&(mbox->pdev->dev), - "Unable to retrieve mbox local resource\n"); - mbox = NULL; - goto exit; - } - dev_dbg(&(mbox->pdev->dev), - "Resource name: %s start: 0x%X, end: 0x%X\n", - resource->name, resource->start, resource->end); - mbox->virtbase_local = ioremap(resource->start, resource_size(resource)); - if (!mbox->virtbase_local) { - dev_err(&(mbox->pdev->dev), "Unable to ioremap local mbox\n"); - mbox = NULL; - goto exit; - } - dev_dbg(&(mbox->pdev->dev), - "ioremapped local physical: (0x%X-0x%X) to virtual: 0x%X\n", - resource->start, resource->end, (u32) mbox->virtbase_peer); - - init_completion(&mbox->buffer_available); - mbox->client_blocked = 0; - - /* Get IRQ for mailbox and allocate it */ - irq = platform_get_irq_byname(mbox->pdev, "mbox_irq"); - if (irq < 0) { - dev_err(&(mbox->pdev->dev), - "Unable to retrieve mbox irq resource\n"); - mbox = NULL; - goto exit; - } - - dev_dbg(&(mbox->pdev->dev), "Allocating irq %d...\n", irq); - res = request_irq(irq, mbox_irq, 0, mbox->name, (void *) mbox); - if (res < 0) { - dev_err(&(mbox->pdev->dev), - "Unable to allocate mbox irq %d\n", irq); - mbox = NULL; - goto exit; - } - - /* Set up mailbox to not launch IRQ on free space in mailbox */ - writel(MBOX_DISABLE_IRQ, mbox->virtbase_peer + MBOX_FIFO_THRES_FREE); - - /* - * Set up mailbox to launch IRQ on new message if we have - * a callback set. If not, do not raise IRQ, but keep message - * in FIFO for manual retrieval - */ - if (mbox_cb != NULL) - writel(MBOX_ENABLE_IRQ, - mbox->virtbase_local + MBOX_FIFO_THRES_OCCUP); - else - writel(MBOX_DISABLE_IRQ, - mbox->virtbase_local + MBOX_FIFO_THRES_OCCUP); - -#if defined(CONFIG_DEBUG_FS) - res = device_create_file(&(mbox->pdev->dev), &dev_attr_fifo); - if (res != 0) - dev_warn(&(mbox->pdev->dev), - "Unable to create mbox sysfs entry"); - - (void) debugfs_create_file("mbox", S_IFREG | S_IRUGO, NULL, - NULL, &mbox_operations); -#endif - - dev_info(&(mbox->pdev->dev), - "Mailbox driver with index %d initiated!\n", mbox_id); - -exit: - return mbox; -} -EXPORT_SYMBOL(mbox_setup); - - -int __init mbox_probe(struct platform_device *pdev) -{ - struct mbox local_mbox; - struct mbox *mbox; - int res = 0; - dev_dbg(&(pdev->dev), "Probing mailbox (pdev = 0x%X)...\n", (u32) pdev); - - memset(&local_mbox, 0x0, sizeof(struct mbox)); - - /* Associate our mbox data with the platform device */ - res = platform_device_add_data(pdev, - (void *) &local_mbox, - sizeof(struct mbox)); - if (res != 0) { - dev_err(&(pdev->dev), - "Unable to allocate driver platform data!\n"); - goto exit; - } - - mbox = (struct mbox *) pdev->dev.platform_data; - mbox->pdev = pdev; - mbox->write_index = 0; - mbox->read_index = 0; - - INIT_LIST_HEAD(&(mbox->list)); - list_add_tail(&(mbox->list), &mboxs); - - sprintf(mbox->name, "%s", MBOX_NAME); - spin_lock_init(&mbox->lock); - - dev_info(&(pdev->dev), "Mailbox driver loaded\n"); - -exit: - return res; -} - -static struct platform_driver mbox_driver = { - .driver = { - .name = MBOX_NAME, - .owner = THIS_MODULE, - }, -}; - -static int __init mbox_init(void) -{ - return platform_driver_probe(&mbox_driver, mbox_probe); -} - -module_init(mbox_init); - -void __exit mbox_exit(void) -{ - platform_driver_unregister(&mbox_driver); -} - -module_exit(mbox_exit); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("MBOX driver"); diff --git a/arch/arm/mach-ux500/pins-db8500.h b/arch/arm/mach-ux500/pins-db8500.h index f923764ee16..062c7acf457 100644 --- a/arch/arm/mach-ux500/pins-db8500.h +++ b/arch/arm/mach-ux500/pins-db8500.h @@ -35,40 +35,40 @@ #define GPIO4_GPIO PIN_CFG(4, GPIO) #define GPIO4_U1_RXD PIN_CFG(4, ALT_A) -#define GPIO4_I2C4_SCL PIN_CFG_PULL(4, ALT_B, UP) +#define GPIO4_I2C4_SCL PIN_CFG(4, ALT_B) #define GPIO4_IP_TRSTn PIN_CFG(4, ALT_C) #define GPIO5_GPIO PIN_CFG(5, GPIO) #define GPIO5_U1_TXD PIN_CFG(5, ALT_A) -#define GPIO5_I2C4_SDA PIN_CFG_PULL(5, ALT_B, UP) +#define GPIO5_I2C4_SDA PIN_CFG(5, ALT_B) #define GPIO5_IP_GPIO6 PIN_CFG(5, ALT_C) #define GPIO6_GPIO PIN_CFG(6, GPIO) #define GPIO6_U1_CTSn PIN_CFG(6, ALT_A) -#define GPIO6_I2C1_SCL PIN_CFG_PULL(6, ALT_B, UP) +#define GPIO6_I2C1_SCL PIN_CFG(6, ALT_B) #define GPIO6_IP_GPIO0 PIN_CFG(6, ALT_C) #define GPIO7_GPIO PIN_CFG(7, GPIO) #define GPIO7_U1_RTSn PIN_CFG(7, ALT_A) -#define GPIO7_I2C1_SDA PIN_CFG_PULL(7, ALT_B, UP) +#define GPIO7_I2C1_SDA PIN_CFG(7, ALT_B) #define GPIO7_IP_GPIO1 PIN_CFG(7, ALT_C) #define GPIO8_GPIO PIN_CFG(8, GPIO) -#define GPIO8_IPI2C_SDA PIN_CFG_PULL(8, ALT_A, UP) -#define GPIO8_I2C2_SDA PIN_CFG_PULL(8, ALT_B, UP) +#define GPIO8_IPI2C_SDA PIN_CFG(8, ALT_A) +#define GPIO8_I2C2_SDA PIN_CFG(8, ALT_B) #define GPIO9_GPIO PIN_CFG(9, GPIO) -#define GPIO9_IPI2C_SCL PIN_CFG_PULL(9, ALT_A, UP) -#define GPIO9_I2C2_SCL PIN_CFG_PULL(9, ALT_B, UP) +#define GPIO9_IPI2C_SCL PIN_CFG(9, ALT_A) +#define GPIO9_I2C2_SCL PIN_CFG(9, ALT_B) #define GPIO10_GPIO PIN_CFG(10, GPIO) -#define GPIO10_IPI2C_SDA PIN_CFG_PULL(10, ALT_A, UP) -#define GPIO10_I2C2_SDA PIN_CFG_PULL(10, ALT_B, UP) +#define GPIO10_IPI2C_SDA PIN_CFG(10, ALT_A) +#define GPIO10_I2C2_SDA PIN_CFG(10, ALT_B) #define GPIO10_IP_GPIO3 PIN_CFG(10, ALT_C) #define GPIO11_GPIO PIN_CFG(11, GPIO) -#define GPIO11_IPI2C_SCL PIN_CFG_PULL(11, ALT_A, UP) -#define GPIO11_I2C2_SCL PIN_CFG_PULL(11, ALT_B, UP) +#define GPIO11_IPI2C_SCL PIN_CFG(11, ALT_A) +#define GPIO11_I2C2_SCL PIN_CFG(11, ALT_B) #define GPIO11_IP_GPIO2 PIN_CFG(11, ALT_C) #define GPIO12_GPIO PIN_CFG(12, GPIO) @@ -87,66 +87,66 @@ #define GPIO16_GPIO PIN_CFG(16, GPIO) #define GPIO16_MSP0_RFS PIN_CFG(16, ALT_A) -#define GPIO16_I2C1_SCL PIN_CFG_PULL(16, ALT_B, UP) +#define GPIO16_I2C1_SCL PIN_CFG(16, ALT_B) #define GPIO16_SLIM0_DAT PIN_CFG(16, ALT_C) #define GPIO17_GPIO PIN_CFG(17, GPIO) #define GPIO17_MSP0_RCK PIN_CFG(17, ALT_A) -#define GPIO17_I2C1_SDA PIN_CFG_PULL(17, ALT_B, UP) +#define GPIO17_I2C1_SDA PIN_CFG(17, ALT_B) #define GPIO17_SLIM0_CLK PIN_CFG(17, ALT_C) #define GPIO18_GPIO PIN_CFG(18, GPIO) -#define GPIO18_MC0_CMDDIR PIN_CFG_PULL(18, ALT_A, UP) +#define GPIO18_MC0_CMDDIR PIN_CFG_INPUT(18, ALT_A, PULLUP) #define GPIO18_U2_RXD PIN_CFG(18, ALT_B) #define GPIO18_MS_IEP PIN_CFG(18, ALT_C) #define GPIO19_GPIO PIN_CFG(19, GPIO) -#define GPIO19_MC0_DAT0DIR PIN_CFG_PULL(19, ALT_A, UP) +#define GPIO19_MC0_DAT0DIR PIN_CFG_INPUT(19, ALT_A, PULLUP) #define GPIO19_U2_TXD PIN_CFG(19, ALT_B) #define GPIO19_MS_DAT0DIR PIN_CFG(19, ALT_C) #define GPIO20_GPIO PIN_CFG(20, GPIO) -#define GPIO20_MC0_DAT2DIR PIN_CFG_PULL(20, ALT_A, UP) +#define GPIO20_MC0_DAT2DIR PIN_CFG_INPUT(20, ALT_A, PULLUP) #define GPIO20_UARTMOD_TXD PIN_CFG(20, ALT_B) #define GPIO20_IP_TRIGOUT PIN_CFG(20, ALT_C) #define GPIO21_GPIO PIN_CFG(21, GPIO) -#define GPIO21_MC0_DAT31DIR PIN_CFG_PULL(21, ALT_A, UP) +#define GPIO21_MC0_DAT31DIR PIN_CFG_INPUT(21, ALT_A, PULLUP) #define GPIO21_MSP0_SCK PIN_CFG(21, ALT_B) #define GPIO21_MS_DAT31DIR PIN_CFG(21, ALT_C) #define GPIO22_GPIO PIN_CFG(22, GPIO) -#define GPIO22_MC0_FBCLK PIN_CFG_PULL(22, ALT_A, UP) +#define GPIO22_MC0_FBCLK PIN_CFG_INPUT(22, ALT_A, PULLUP) #define GPIO22_UARTMOD_RXD PIN_CFG(22, ALT_B) #define GPIO22_MS_FBCLK PIN_CFG(22, ALT_C) #define GPIO23_GPIO PIN_CFG(23, GPIO) -#define GPIO23_MC0_CLK PIN_CFG_PULL(23, ALT_A, UP) +#define GPIO23_MC0_CLK PIN_CFG_INPUT(23, ALT_A, PULLUP) #define GPIO23_STMMOD_CLK PIN_CFG(23, ALT_B) #define GPIO23_MS_CLK PIN_CFG(23, ALT_C) #define GPIO24_GPIO PIN_CFG(24, GPIO) -#define GPIO24_MC0_CMD PIN_CFG_PULL(24, ALT_A, UP) +#define GPIO24_MC0_CMD PIN_CFG_INPUT(24, ALT_A, PULLUP) #define GPIO24_UARTMOD_RXD PIN_CFG(24, ALT_B) #define GPIO24_MS_BS PIN_CFG(24, ALT_C) #define GPIO25_GPIO PIN_CFG(25, GPIO) -#define GPIO25_MC0_DAT0 PIN_CFG_PULL(25, ALT_A, UP) +#define GPIO25_MC0_DAT0 PIN_CFG_INPUT(25, ALT_A, PULLUP) #define GPIO25_STMMOD_DAT0 PIN_CFG(25, ALT_B) #define GPIO25_MS_DAT0 PIN_CFG(25, ALT_C) #define GPIO26_GPIO PIN_CFG(26, GPIO) -#define GPIO26_MC0_DAT1 PIN_CFG_PULL(26, ALT_A, UP) +#define GPIO26_MC0_DAT1 PIN_CFG_INPUT(26, ALT_A, PULLUP) #define GPIO26_STMMOD_DAT1 PIN_CFG(26, ALT_B) #define GPIO26_MS_DAT1 PIN_CFG(26, ALT_C) #define GPIO27_GPIO PIN_CFG(27, GPIO) -#define GPIO27_MC0_DAT2 PIN_CFG_PULL(27, ALT_A, UP) +#define GPIO27_MC0_DAT2 PIN_CFG_INPUT(27, ALT_A, PULLUP) #define GPIO27_STMMOD_DAT2 PIN_CFG(27, ALT_B) #define GPIO27_MS_DAT2 PIN_CFG(27, ALT_C) #define GPIO28_GPIO PIN_CFG(28, GPIO) -#define GPIO28_MC0_DAT3 PIN_CFG_PULL(28, ALT_A, UP) +#define GPIO28_MC0_DAT3 PIN_CFG_INPUT(28, ALT_A, PULLUP) #define GPIO28_STMMOD_DAT3 PIN_CFG(28, ALT_B) #define GPIO28_MS_DAT3 PIN_CFG(28, ALT_C) @@ -357,48 +357,48 @@ #define GPIO97_MC5_DAT7 PIN_CFG(97, ALT_C) #define GPIO128_GPIO PIN_CFG(128, GPIO) -#define GPIO128_MC2_CLK PIN_CFG_PULL(128, ALT_A, UP) +#define GPIO128_MC2_CLK PIN_CFG_INPUT(128, ALT_A, PULLUP) #define GPIO128_SM_CKO PIN_CFG(128, ALT_B) #define GPIO129_GPIO PIN_CFG(129, GPIO) -#define GPIO129_MC2_CMD PIN_CFG_PULL(129, ALT_A, UP) +#define GPIO129_MC2_CMD PIN_CFG_INPUT(129, ALT_A, PULLUP) #define GPIO129_SM_WAIT0n PIN_CFG(129, ALT_B) #define GPIO130_GPIO PIN_CFG(130, GPIO) -#define GPIO130_MC2_FBCLK PIN_CFG_PULL(130, ALT_A, UP) +#define GPIO130_MC2_FBCLK PIN_CFG_INPUT(130, ALT_A, PULLUP) #define GPIO130_SM_FBCLK PIN_CFG(130, ALT_B) #define GPIO130_MC2_RSTN PIN_CFG(130, ALT_C) #define GPIO131_GPIO PIN_CFG(131, GPIO) -#define GPIO131_MC2_DAT0 PIN_CFG_PULL(131, ALT_A, UP) +#define GPIO131_MC2_DAT0 PIN_CFG_INPUT(131, ALT_A, PULLUP) #define GPIO131_SM_ADQ8 PIN_CFG(131, ALT_B) #define GPIO132_GPIO PIN_CFG(132, GPIO) -#define GPIO132_MC2_DAT1 PIN_CFG_PULL(132, ALT_A, UP) +#define GPIO132_MC2_DAT1 PIN_CFG_INPUT(132, ALT_A, PULLUP) #define GPIO132_SM_ADQ9 PIN_CFG(132, ALT_B) #define GPIO133_GPIO PIN_CFG(133, GPIO) -#define GPIO133_MC2_DAT2 PIN_CFG_PULL(133, ALT_A, UP) +#define GPIO133_MC2_DAT2 PIN_CFG_INPUT(133, ALT_A, PULLUP) #define GPIO133_SM_ADQ10 PIN_CFG(133, ALT_B) #define GPIO134_GPIO PIN_CFG(134, GPIO) -#define GPIO134_MC2_DAT3 PIN_CFG_PULL(134, ALT_A, UP) +#define GPIO134_MC2_DAT3 PIN_CFG_INPUT(134, ALT_A, PULLUP) #define GPIO134_SM_ADQ11 PIN_CFG(134, ALT_B) #define GPIO135_GPIO PIN_CFG(135, GPIO) -#define GPIO135_MC2_DAT4 PIN_CFG_PULL(135, ALT_A, UP) +#define GPIO135_MC2_DAT4 PIN_CFG_INPUT(135, ALT_A, PULLUP) #define GPIO135_SM_ADQ12 PIN_CFG(135, ALT_B) #define GPIO136_GPIO PIN_CFG(136, GPIO) -#define GPIO136_MC2_DAT5 PIN_CFG_PULL(136, ALT_A, UP) +#define GPIO136_MC2_DAT5 PIN_CFG_INPUT(136, ALT_A, PULLUP) #define GPIO136_SM_ADQ13 PIN_CFG(136, ALT_B) #define GPIO137_GPIO PIN_CFG(137, GPIO) -#define GPIO137_MC2_DAT6 PIN_CFG_PULL(137, ALT_A, UP) +#define GPIO137_MC2_DAT6 PIN_CFG_INPUT(137, ALT_A, PULLUP) #define GPIO137_SM_ADQ14 PIN_CFG(137, ALT_B) #define GPIO138_GPIO PIN_CFG(138, GPIO) -#define GPIO138_MC2_DAT7 PIN_CFG_PULL(138, ALT_A, UP) +#define GPIO138_MC2_DAT7 PIN_CFG_INPUT(138, ALT_A, PULLUP) #define GPIO138_SM_ADQ15 PIN_CFG(138, ALT_B) #define GPIO139_GPIO PIN_CFG(139, GPIO) @@ -434,10 +434,10 @@ #define GPIO146_SSP0_TXD PIN_CFG(146, ALT_A) #define GPIO147_GPIO PIN_CFG(147, GPIO) -#define GPIO147_I2C0_SCL PIN_CFG_PULL(147, ALT_A, UP) +#define GPIO147_I2C0_SCL PIN_CFG(147, ALT_A) #define GPIO148_GPIO PIN_CFG(148, GPIO) -#define GPIO148_I2C0_SDA PIN_CFG_PULL(148, ALT_A, UP) +#define GPIO148_I2C0_SDA PIN_CFG(148, ALT_A) #define GPIO149_GPIO PIN_CFG(149, GPIO) #define GPIO149_IP_GPIO0 PIN_CFG(149, ALT_A) @@ -459,82 +459,82 @@ #define GPIO152_KP_O9 PIN_CFG(152, ALT_C) #define GPIO153_GPIO PIN_CFG(153, GPIO) -#define GPIO153_KP_I7 PIN_CFG_PULL(153, ALT_A, DOWN) +#define GPIO153_KP_I7 PIN_CFG(153, ALT_A) #define GPIO153_LCD_D24 PIN_CFG(153, ALT_B) #define GPIO153_U2_RXD PIN_CFG(153, ALT_C) #define GPIO154_GPIO PIN_CFG(154, GPIO) -#define GPIO154_KP_I6 PIN_CFG_PULL(154, ALT_A, DOWN) +#define GPIO154_KP_I6 PIN_CFG(154, ALT_A) #define GPIO154_LCD_D25 PIN_CFG(154, ALT_B) #define GPIO154_U2_TXD PIN_CFG(154, ALT_C) #define GPIO155_GPIO PIN_CFG(155, GPIO) -#define GPIO155_KP_I5 PIN_CFG_PULL(155, ALT_A, DOWN) +#define GPIO155_KP_I5 PIN_CFG(155, ALT_A) #define GPIO155_LCD_D26 PIN_CFG(155, ALT_B) #define GPIO155_STMAPE_CLK PIN_CFG(155, ALT_C) #define GPIO156_GPIO PIN_CFG(156, GPIO) -#define GPIO156_KP_I4 PIN_CFG_PULL(156, ALT_A, DOWN) +#define GPIO156_KP_I4 PIN_CFG(156, ALT_A) #define GPIO156_LCD_D27 PIN_CFG(156, ALT_B) #define GPIO156_STMAPE_DAT3 PIN_CFG(156, ALT_C) #define GPIO157_GPIO PIN_CFG(157, GPIO) -#define GPIO157_KP_O7 PIN_CFG_PULL(157, ALT_A, UP) +#define GPIO157_KP_O7 PIN_CFG(157, ALT_A) #define GPIO157_LCD_D28 PIN_CFG(157, ALT_B) #define GPIO157_STMAPE_DAT2 PIN_CFG(157, ALT_C) #define GPIO158_GPIO PIN_CFG(158, GPIO) -#define GPIO158_KP_O6 PIN_CFG_PULL(158, ALT_A, UP) +#define GPIO158_KP_O6 PIN_CFG(158, ALT_A) #define GPIO158_LCD_D29 PIN_CFG(158, ALT_B) #define GPIO158_STMAPE_DAT1 PIN_CFG(158, ALT_C) #define GPIO159_GPIO PIN_CFG(159, GPIO) -#define GPIO159_KP_O5 PIN_CFG_PULL(159, ALT_A, UP) +#define GPIO159_KP_O5 PIN_CFG(159, ALT_A) #define GPIO159_LCD_D30 PIN_CFG(159, ALT_B) #define GPIO159_STMAPE_DAT0 PIN_CFG(159, ALT_C) #define GPIO160_GPIO PIN_CFG(160, GPIO) -#define GPIO160_KP_O4 PIN_CFG_PULL(160, ALT_A, UP) +#define GPIO160_KP_O4 PIN_CFG(160, ALT_A) #define GPIO160_LCD_D31 PIN_CFG(160, ALT_B) #define GPIO160_NONE PIN_CFG(160, ALT_C) #define GPIO161_GPIO PIN_CFG(161, GPIO) -#define GPIO161_KP_I3 PIN_CFG_PULL(161, ALT_A, DOWN) +#define GPIO161_KP_I3 PIN_CFG(161, ALT_A) #define GPIO161_LCD_D32 PIN_CFG(161, ALT_B) #define GPIO161_UARTMOD_RXD PIN_CFG(161, ALT_C) #define GPIO162_GPIO PIN_CFG(162, GPIO) -#define GPIO162_KP_I2 PIN_CFG_PULL(162, ALT_A, DOWN) +#define GPIO162_KP_I2 PIN_CFG(162, ALT_A) #define GPIO162_LCD_D33 PIN_CFG(162, ALT_B) #define GPIO162_UARTMOD_TXD PIN_CFG(162, ALT_C) #define GPIO163_GPIO PIN_CFG(163, GPIO) -#define GPIO163_KP_I1 PIN_CFG_PULL(163, ALT_A, DOWN) +#define GPIO163_KP_I1 PIN_CFG(163, ALT_A) #define GPIO163_LCD_D34 PIN_CFG(163, ALT_B) #define GPIO163_STMMOD_CLK PIN_CFG(163, ALT_C) #define GPIO164_GPIO PIN_CFG(164, GPIO) -#define GPIO164_KP_I0 PIN_CFG_PULL(164, ALT_A, UP) +#define GPIO164_KP_I0 PIN_CFG(164, ALT_A) #define GPIO164_LCD_D35 PIN_CFG(164, ALT_B) #define GPIO164_STMMOD_DAT3 PIN_CFG(164, ALT_C) #define GPIO165_GPIO PIN_CFG(165, GPIO) -#define GPIO165_KP_O3 PIN_CFG_PULL(165, ALT_A, UP) +#define GPIO165_KP_O3 PIN_CFG(165, ALT_A) #define GPIO165_LCD_D36 PIN_CFG(165, ALT_B) #define GPIO165_STMMOD_DAT2 PIN_CFG(165, ALT_C) #define GPIO166_GPIO PIN_CFG(166, GPIO) -#define GPIO166_KP_O2 PIN_CFG_PULL(166, ALT_A, UP) +#define GPIO166_KP_O2 PIN_CFG(166, ALT_A) #define GPIO166_LCD_D37 PIN_CFG(166, ALT_B) #define GPIO166_STMMOD_DAT1 PIN_CFG(166, ALT_C) #define GPIO167_GPIO PIN_CFG(167, GPIO) -#define GPIO167_KP_O1 PIN_CFG_PULL(167, ALT_A, UP) +#define GPIO167_KP_O1 PIN_CFG(167, ALT_A) #define GPIO167_LCD_D38 PIN_CFG(167, ALT_B) #define GPIO167_STMMOD_DAT0 PIN_CFG(167, ALT_C) #define GPIO168_GPIO PIN_CFG(168, GPIO) -#define GPIO168_KP_O0 PIN_CFG_PULL(168, ALT_A, UP) +#define GPIO168_KP_O0 PIN_CFG(168, ALT_A) #define GPIO168_LCD_D39 PIN_CFG(168, ALT_B) #define GPIO168_NONE PIN_CFG(168, ALT_C) @@ -569,39 +569,39 @@ #define GPIO196_MSP2_RXD PIN_CFG(196, ALT_A) #define GPIO197_GPIO PIN_CFG(197, GPIO) -#define GPIO197_MC4_DAT3 PIN_CFG_PULL(197, ALT_A, UP) +#define GPIO197_MC4_DAT3 PIN_CFG_INPUT(197, ALT_A, PULLUP) #define GPIO198_GPIO PIN_CFG(198, GPIO) -#define GPIO198_MC4_DAT2 PIN_CFG_PULL(198, ALT_A, UP) +#define GPIO198_MC4_DAT2 PIN_CFG_INPUT(198, ALT_A, PULLUP) #define GPIO199_GPIO PIN_CFG(199, GPIO) -#define GPIO199_MC4_DAT1 PIN_CFG_PULL(199, ALT_A, UP) +#define GPIO199_MC4_DAT1 PIN_CFG_INPUT(199, ALT_A, PULLUP) #define GPIO200_GPIO PIN_CFG(200, GPIO) -#define GPIO200_MC4_DAT0 PIN_CFG_PULL(200, ALT_A, UP) +#define GPIO200_MC4_DAT0 PIN_CFG_INPUT(200, ALT_A, PULLUP) #define GPIO201_GPIO PIN_CFG(201, GPIO) -#define GPIO201_MC4_CMD PIN_CFG_PULL(201, ALT_A, UP) +#define GPIO201_MC4_CMD PIN_CFG_INPUT(201, ALT_A, PULLUP) #define GPIO202_GPIO PIN_CFG(202, GPIO) -#define GPIO202_MC4_FBCLK PIN_CFG_PULL(202, ALT_A, UP) +#define GPIO202_MC4_FBCLK PIN_CFG_INPUT(202, ALT_A, PULLUP) #define GPIO202_PWL PIN_CFG(202, ALT_B) #define GPIO202_MC4_RSTN PIN_CFG(202, ALT_C) #define GPIO203_GPIO PIN_CFG(203, GPIO) -#define GPIO203_MC4_CLK PIN_CFG_PULL(203, ALT_A, UP) +#define GPIO203_MC4_CLK PIN_CFG_INPUT(203, ALT_A, PULLUP) #define GPIO204_GPIO PIN_CFG(204, GPIO) -#define GPIO204_MC4_DAT7 PIN_CFG_PULL(204, ALT_A, UP) +#define GPIO204_MC4_DAT7 PIN_CFG_INPUT(204, ALT_A, PULLUP) #define GPIO205_GPIO PIN_CFG(205, GPIO) -#define GPIO205_MC4_DAT6 PIN_CFG_PULL(205, ALT_A, UP) +#define GPIO205_MC4_DAT6 PIN_CFG_INPUT(205, ALT_A, PULLUP) #define GPIO206_GPIO PIN_CFG(206, GPIO) -#define GPIO206_MC4_DAT5 PIN_CFG_PULL(206, ALT_A, UP) +#define GPIO206_MC4_DAT5 PIN_CFG_INPUT(206, ALT_A, PULLUP) #define GPIO207_GPIO PIN_CFG(207, GPIO) -#define GPIO207_MC4_DAT4 PIN_CFG_PULL(207, ALT_A, UP) +#define GPIO207_MC4_DAT4 PIN_CFG_INPUT(207, ALT_A, PULLUP) #define GPIO208_GPIO PIN_CFG(208, GPIO) #define GPIO208_MC1_CLK PIN_CFG(208, ALT_A) @@ -632,21 +632,25 @@ #define GPIO215_MC1_CMDDIR PIN_CFG(215, ALT_A) #define GPIO215_MC3_DAT2DIR PIN_CFG(215, ALT_B) #define GPIO215_CLKOUT1 PIN_CFG(215, ALT_C) +#define GPIO215_SPI2_TXD PIN_CFG(215, ALT_C) #define GPIO216_GPIO PIN_CFG(216, GPIO) #define GPIO216_MC1_DAT2DIR PIN_CFG(216, ALT_A) #define GPIO216_MC3_CMDDIR PIN_CFG(216, ALT_B) -#define GPIO216_I2C3_SDA PIN_CFG_PULL(216, ALT_C, UP) +#define GPIO216_I2C3_SDA PIN_CFG(216, ALT_C) +#define GPIO216_SPI2_FRM PIN_CFG(216, ALT_C) #define GPIO217_GPIO PIN_CFG(217, GPIO) #define GPIO217_MC1_DAT0DIR PIN_CFG(217, ALT_A) #define GPIO217_MC3_DAT31DIR PIN_CFG(217, ALT_B) #define GPIO217_CLKOUT2 PIN_CFG(217, ALT_C) +#define GPIO217_SPI2_CLK PIN_CFG(217, ALT_C) #define GPIO218_GPIO PIN_CFG(218, GPIO) #define GPIO218_MC1_DAT31DIR PIN_CFG(218, ALT_A) #define GPIO218_MC3_DAT0DIR PIN_CFG(218, ALT_B) -#define GPIO218_I2C3_SCL PIN_CFG_PULL(218, ALT_C, UP) +#define GPIO218_I2C3_SCL PIN_CFG(218, ALT_C) +#define GPIO218_SPI2_RXD PIN_CFG(218, ALT_C) #define GPIO219_GPIO PIN_CFG(219, GPIO) #define GPIO219_HSIR_FLA0 PIN_CFG(219, ALT_A) @@ -694,12 +698,12 @@ #define GPIO229_GPIO PIN_CFG(229, GPIO) #define GPIO229_CLKOUT1 PIN_CFG(229, ALT_A) #define GPIO229_PWL PIN_CFG(229, ALT_B) -#define GPIO229_I2C3_SDA PIN_CFG_PULL(229, ALT_C, UP) +#define GPIO229_I2C3_SDA PIN_CFG(229, ALT_C) #define GPIO230_GPIO PIN_CFG(230, GPIO) #define GPIO230_CLKOUT2 PIN_CFG(230, ALT_A) #define GPIO230_PWL PIN_CFG(230, ALT_B) -#define GPIO230_I2C3_SCL PIN_CFG_PULL(230, ALT_C, UP) +#define GPIO230_I2C3_SCL PIN_CFG(230, ALT_C) #define GPIO256_GPIO PIN_CFG(256, GPIO) #define GPIO256_USB_NXT PIN_CFG(256, ALT_A) diff --git a/arch/arm/mach-ux500/pins.c b/arch/arm/mach-ux500/pins.c new file mode 100644 index 00000000000..83a041d26f4 --- /dev/null +++ b/arch/arm/mach-ux500/pins.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/err.h> +#include <plat/pincfg.h> + +#include "pins.h" + +static LIST_HEAD(pin_lookups); +static DEFINE_MUTEX(pin_lookups_mutex); +static DEFINE_SPINLOCK(pins_lock); + +void __init ux500_pins_add(struct ux500_pin_lookup *pl, size_t num) +{ + mutex_lock(&pin_lookups_mutex); + + while (num--) { + list_add_tail(&pl->node, &pin_lookups); + pl++; + } + + mutex_unlock(&pin_lookups_mutex); +} + +struct ux500_pins *ux500_pins_get(const char *name) +{ + struct ux500_pins *pins = NULL; + struct ux500_pin_lookup *pl; + + mutex_lock(&pin_lookups_mutex); + + list_for_each_entry(pl, &pin_lookups, node) { + if (!strcmp(pl->name, name)) { + pins = pl->pins; + goto out; + } + } + +out: + mutex_unlock(&pin_lookups_mutex); + return pins; +} + +int ux500_pins_enable(struct ux500_pins *pins) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&pins_lock, flags); + + if (pins->usage++ == 0) + ret = nmk_config_pins(pins->cfg, pins->num); + + spin_unlock_irqrestore(&pins_lock, flags); + return ret; +} + +int ux500_pins_disable(struct ux500_pins *pins) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&pins_lock, flags); + + if (WARN_ON(pins->usage == 0)) + goto out; + + if (--pins->usage == 0) + ret = nmk_config_pins_sleep(pins->cfg, pins->num); + +out: + spin_unlock_irqrestore(&pins_lock, flags); + return ret; +} + +void ux500_pins_put(struct ux500_pins *pins) +{ + WARN_ON(!pins); +} + +#ifdef CONFIG_DEBUG_FS +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/gpio/nomadik.h> + +#include <mach/gpio.h> + +static void show_pin(struct seq_file *s, pin_cfg_t pin) +{ + static const char *afnames[] = { + [NMK_GPIO_ALT_GPIO] = "GPIO", + [NMK_GPIO_ALT_A] = "A", + [NMK_GPIO_ALT_B] = "B", + [NMK_GPIO_ALT_C] = "C" + }; + static const char *pullnames[] = { + [NMK_GPIO_PULL_NONE] = "none", + [NMK_GPIO_PULL_UP] = "up", + [NMK_GPIO_PULL_DOWN] = "down", + [3] /* illegal */ = "??" + }; + + int pin_num = PIN_NUM(pin); + int pull = PIN_PULL(pin); + int af = PIN_ALT(pin); + int slpm = PIN_SLPM(pin); + int output = PIN_DIR(pin); + int val = PIN_VAL(pin); + int slpm_pull = PIN_SLPM_PULL(pin); + int slpm_dir = PIN_SLPM_DIR(pin); + int slpm_val = PIN_SLPM_VAL(pin); + int slpm_pdis = PIN_SLPM_PDIS(pin); + + seq_printf(s, + " pin %d [%#lx]: af %s, pull %s (%s%s) - slpm: %s%s%s%s%s\n", + pin_num, pin, afnames[af], + pullnames[pull], + output ? "output " : "input", + output ? (val ? "high" : "low") : "", + slpm ? "no-change/no-wakeup " : "input/wakeup ", + slpm_dir ? (slpm_dir == 1 ? "input " : "output " ) : "", + slpm_dir == 1 ? (slpm_pull == 0 ? "pull: none ": + (slpm_pull == NMK_GPIO_PULL_UP ? + "pull: up " : "pull: down ") ): "", + slpm_dir == 2 ? (slpm_val == 1 ? "low " : "high " ) : "", + slpm_pdis ? (slpm_pdis == 1 ? "pdis: dis" : "pdis: en") : + "pdis: no change"); +} + +static int pins_dbg_show(struct seq_file *s, void *iter) +{ + struct ux500_pin_lookup *pl; + int i; + bool *pins; + int prev = -2; + int first = 0; + + pins = kzalloc(sizeof(bool) * NOMADIK_NR_GPIO, GFP_KERNEL); + + mutex_lock(&pin_lookups_mutex); + + list_for_each_entry(pl, &pin_lookups, node) { + seq_printf(s, "\n%s (%d) usage: %d\n", + pl->name, pl->pins->num, pl->pins->usage); + for (i = 0; i < pl->pins->num; i++) { + show_pin(s, pl->pins->cfg[i]); + pins[PIN_NUM(pl->pins->cfg[i])] = true; + } + } + mutex_unlock(&pin_lookups_mutex); + + seq_printf(s, "\nSummary allocated pins:\n"); + for (i = 0; i < NOMADIK_NR_GPIO; i++) { + if (prev == i - 1) { + if (pins[i]) + prev = i; + else + if (prev > 0) { + if (first != prev) + seq_printf(s, "-%d, ", prev); + else + seq_printf(s, ", "); + } + continue; + } + if (pins[i]) { + seq_printf(s, "%d", i); + prev = i; + first = i; + } + } + if (prev == i - 1 && first != prev) + seq_printf(s, "-%d", prev); + + seq_printf(s, "\n"); + + return 0; +} + +static int pins_dbg_open(struct inode *inode, + struct file *file) +{ + return single_open(file, pins_dbg_show, inode->i_private); +} + +static const struct file_operations pins_fops = { + .open = pins_dbg_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int __init pins_dbg_init(void) +{ + (void) debugfs_create_file("pins", S_IRUGO, + NULL, + NULL, + &pins_fops); + return 0; +} +late_initcall(pins_dbg_init); +#endif diff --git a/arch/arm/mach-ux500/pins.h b/arch/arm/mach-ux500/pins.h new file mode 100644 index 00000000000..0fa65cc6b96 --- /dev/null +++ b/arch/arm/mach-ux500/pins.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef __MACH_UX500_PINS_H +#define __MACH_UX500_PINS_H + +#include <linux/list.h> +#include <plat/pincfg.h> + +#define PIN_LOOKUP(_name, _pins) \ +{ \ + .name = _name, \ + .pins = _pins, \ +} + +#define UX500_PINS(name, pins...) \ +struct ux500_pins name = { \ + .cfg = (pin_cfg_t[]) {pins}, \ + .num = ARRAY_SIZE(((pin_cfg_t[]) {pins})), \ +} + +struct ux500_pins { + int usage; + int num; + pin_cfg_t *cfg; +}; + +struct ux500_pin_lookup { + struct list_head node; + const char *name; + struct ux500_pins *pins; +}; + +void __init ux500_pins_add(struct ux500_pin_lookup *pl, size_t num); +struct ux500_pins *ux500_pins_get(const char *name); +int ux500_pins_enable(struct ux500_pins *pins); +int ux500_pins_disable(struct ux500_pins *pins); +void ux500_pins_put(struct ux500_pins *pins); +int pins_for_u9500(void); + +#endif diff --git a/arch/arm/mach-ux500/pm/Kconfig b/arch/arm/mach-ux500/pm/Kconfig new file mode 100644 index 00000000000..195a1d7cad2 --- /dev/null +++ b/arch/arm/mach-ux500/pm/Kconfig @@ -0,0 +1,68 @@ +config DBX500_PRCMU_QOS_POWER + bool "DBX500 PRCMU power QoS support" + depends on (MFD_DB5500_PRCMU || MFD_DB8500_PRCMU) + default y + help + Add support for PRCMU power Quality of Service + +config UX500_CONTEXT + bool "Context save/restore support for UX500" + depends on (UX500_SOC_DB8500 || UX500_SOC_DB5500) && PM + help + This is needed for ApSleep and deeper sleep states. + +config UX500_PM_PERFORMANCE + bool "Performance supervision" + depends on DBX500_PRCMU_QOS_POWER + default y + help + Enable supervision of events which may require a boost + of platform performance. + +config UX500_CONSOLE_UART_GPIO_PIN + int "The pin number of the console UART GPIO pin" + default 29 + depends on UX500_SUSPEND_DBG_WAKE_ON_UART || UX500_CPUIDLE_DEBUG + help + GPIO pin number of the GPIO pin connected to the console UART RX line. + +config UX500_SUSPEND + bool "Suspend to mem and standby support" + depends on (UX500_SOC_DB8500 || UX500_SOC_DB5500) && PM && SUSPEND + select UX500_CONTEXT + help + Add support for suspend. + +config UX500_SUSPEND_STANDBY + bool "Suspend Standby goes to ApSleep" + depends on UX500_SUSPEND + help + If yes, echo standby > /sys/power/state puts the system into ApSleep. + +config UX500_SUSPEND_MEM + bool "Suspend Mem goes to ApDeepSleep" + depends on UX500_SUSPEND + help + If yes, echo mem > /sys/power/state puts the system into ApDeepSleep else + it will do the same as echo standby > /sys/power/state. + +config UX500_SUSPEND_DBG + bool "Suspend debug" + depends on UX500_SUSPEND && DEBUG_FS + help + Add debug support for suspend. + +config UX500_SUSPEND_DBG_WAKE_ON_UART + bool "Suspend wakes on console UART" + depends on UX500_SUSPEND_DBG + help + Wake up on uart interrupts. Makes it possible for the console to wake up system. + +config UX500_USECASE_GOVERNOR + bool "UX500 use-case governor" + depends on (UX500_SOC_DB8500 || UX500_SOC_DB5500) && \ + (CPU_FREQ && CPU_IDLE && HOTPLUG_CPU && \ + EARLYSUSPEND && UX500_L2X0_PREFETCH_CTRL && PM) + default y + help + Adjusts CPU_IDLE, CPU_FREQ, HOTPLUG_CPU and L2 cache parameters diff --git a/arch/arm/mach-ux500/pm/Makefile b/arch/arm/mach-ux500/pm/Makefile new file mode 100644 index 00000000000..c0af28e5d3e --- /dev/null +++ b/arch/arm/mach-ux500/pm/Makefile @@ -0,0 +1,12 @@ +# +# Power save related files +# +obj-y := pm.o runtime.o + +obj-$(CONFIG_DBX500_PRCMU_QOS_POWER) += prcmu-qos-power.o +obj-$(CONFIG_UX500_CONTEXT) += context.o context_arm.o context-db8500.o context-db5500.o +obj-$(CONFIG_UX500_CPUIDLE) += timer.o +obj-$(CONFIG_UX500_SUSPEND) += suspend.o +obj-$(CONFIG_UX500_SUSPEND_DBG) += suspend_dbg.o +obj-$(CONFIG_UX500_PM_PERFORMANCE) += performance.o +obj-$(CONFIG_UX500_USECASE_GOVERNOR) += usecase_gov.o diff --git a/arch/arm/mach-ux500/pm/context-db5500.c b/arch/arm/mach-ux500/pm/context-db5500.c new file mode 100644 index 00000000000..8075bd9e113 --- /dev/null +++ b/arch/arm/mach-ux500/pm/context-db5500.c @@ -0,0 +1,404 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com>, + * Rickard Andersson <rickard.andersson@stericsson.com>, + * Sundar Iyer <sundar.iyer@stericsson.com>, + * ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#include <linux/io.h> + +#include <mach/hardware.h> +#include <mach/context.h> + +/* These registers are DB5500 specific */ +#define NODE_HIBW1_ESRAM_IN_0_PRIORITY 0x0 +#define NODE_HIBW1_ESRAM_IN_1_PRIORITY 0x4 + +#define NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT 0x18 +#define NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT 0x1C +#define NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT 0x20 + +#define NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT 0x24 +#define NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT 0x28 +#define NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT 0x2C + +#define NODE_HIBW1_DDR_IN_0_PRIORITY 0x400 +#define NODE_HIBW1_DDR_IN_1_PRIORITY 0x404 +#define NODE_HIBW1_DDR_IN_2_PRIORITY 0x408 + +#define NODE_HIBW1_DDR_IN_0_LIMIT 0x424 +#define NODE_HIBW1_DDR_IN_1_LIMIT 0x428 +#define NODE_HIBW1_DDR_IN_2_LIMIT 0x42C + +#define NODE_HIBW1_DDR_OUT_0_PRIORITY 0x430 + +#define NODE_HIBW2_ESRAM_IN_0_PRIORITY 0x800 +#define NODE_HIBW2_ESRAM_IN_1_PRIORITY 0x804 + +#define NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT 0x818 +#define NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT 0x81C +#define NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT 0x820 + +#define NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT 0x824 +#define NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT 0x828 +#define NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT 0x82C + +#define NODE_HIBW2_DDR_IN_0_PRIORITY 0xC00 +#define NODE_HIBW2_DDR_IN_1_PRIORITY 0xC04 +#define NODE_HIBW2_DDR_IN_2_PRIORITY 0xC08 +#define NODE_HIBW2_DDR_IN_3_PRIORITY 0xC0C + +#define NODE_HIBW2_DDR_IN_0_LIMIT 0xC30 +#define NODE_HIBW2_DDR_IN_1_LIMIT 0xC34 +#define NODE_HIBW2_DDR_IN_2_LIMIT 0xC38 +#define NODE_HIBW2_DDR_IN_3_LIMIT 0xC3C + +#define NODE_HIBW2_DDR_OUT_0_PRIORITY 0xC40 + +#define NODE_ESRAM0_IN_0_PRIORITY 0x1000 +#define NODE_ESRAM0_IN_1_PRIORITY 0x1004 +#define NODE_ESRAM0_IN_2_PRIORITY 0x1008 + +#define NODE_ESRAM0_IN_0_LIMIT 0x1024 +#define NODE_ESRAM0_IN_1_LIMIT 0x1028 +#define NODE_ESRAM0_IN_2_LIMIT 0x102C +#define NODE_ESRAM0_OUT_0_PRIORITY 0x1030 + +#define NODE_ESRAM1_2_IN_0_PRIORITY 0x1400 +#define NODE_ESRAM1_2_IN_1_PRIORITY 0x1404 +#define NODE_ESRAM1_2_IN_2_PRIORITY 0x1408 + +#define NODE_ESRAM1_2_IN_0_ARB_1_LIMIT 0x1424 +#define NODE_ESRAM1_2_IN_1_ARB_1_LIMIT 0x1428 +#define NODE_ESRAM1_2_IN_2_ARB_1_LIMIT 0x142C +#define NODE_ESRAM1_2_OUT_0_PRIORITY 0x1430 + +#define NODE_ESRAM3_4_IN_0_PRIORITY 0x1800 +#define NODE_ESRAM3_4_IN_1_PRIORITY 0x1804 +#define NODE_ESRAM3_4_IN_2_PRIORITY 0x1808 + +#define NODE_ESRAM3_4_IN_0_ARB_1_LIMIT 0x1824 +#define NODE_ESRAM3_4_IN_1_ARB_1_LIMIT 0x1828 +#define NODE_ESRAM3_4_IN_2_ARB_1_LIMIT 0x182C +#define NODE_ESRAM3_4_OUT_0_PRIORITY 0x1830 + +/* + * Save ICN (Interconnect or Interconnect nodes) configuration registers + * TODO: This can be optimized, for example if we have + * a static ICN configuration. + */ + +static struct { + void __iomem *base; + u32 hibw1_esram_in_pri[2]; + u32 hibw1_esram_in0_arb[3]; + u32 hibw1_esram_in1_arb[3]; + u32 hibw1_ddr_in_prio[3]; + u32 hibw1_ddr_in_limit[3]; + u32 hibw1_ddr_out_prio_reg; + + /* HiBw2 node registers */ + u32 hibw2_esram_in_pri[2]; + u32 hibw2_esram_in0_arblimit[3]; + u32 hibw2_esram_in1_arblimit[3]; + u32 hibw2_ddr_in_prio[4]; + u32 hibw2_ddr_in_limit[4]; + u32 hibw2_ddr_out_prio_reg; + + /* ESRAM node registers */ + u32 esram_in_prio[3]; + u32 esram_in_lim[3]; + u32 esram_out_prio_reg; + + u32 esram12_in_prio[3]; + u32 esram12_in_arb_lim[3]; + u32 esram12_out_prio_reg; + + u32 esram34_in_prio[3]; + u32 esram34_in_arb_lim[3]; + u32 esram34_out_prio; +} context_icn; + + +void u5500_context_save_icn(void) +{ + /* hibw1 */ + context_icn.hibw1_esram_in_pri[0] = + readl(context_icn.base + NODE_HIBW1_ESRAM_IN_0_PRIORITY); + context_icn.hibw1_esram_in_pri[1] = + readl(context_icn.base + NODE_HIBW1_ESRAM_IN_1_PRIORITY); + + context_icn.hibw1_esram_in0_arb[0] = + readl(context_icn.base + NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT); + context_icn.hibw1_esram_in0_arb[1] = + readl(context_icn.base + NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT); + context_icn.hibw1_esram_in0_arb[2] = + readl(context_icn.base + NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT); + + context_icn.hibw1_esram_in1_arb[0] = + readl(context_icn.base + NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT); + context_icn.hibw1_esram_in1_arb[1] = + readl(context_icn.base + NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT); + context_icn.hibw1_esram_in1_arb[2] = + readl(context_icn.base + NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT); + + context_icn.hibw1_ddr_in_prio[0] = + readl(context_icn.base + NODE_HIBW1_DDR_IN_0_PRIORITY); + context_icn.hibw1_ddr_in_prio[1] = + readl(context_icn.base + NODE_HIBW1_DDR_IN_1_PRIORITY); + context_icn.hibw1_ddr_in_prio[2] = + readl(context_icn.base + NODE_HIBW1_DDR_IN_2_PRIORITY); + + context_icn.hibw1_ddr_in_limit[0] = + readl(context_icn.base + NODE_HIBW1_DDR_IN_0_LIMIT); + context_icn.hibw1_ddr_in_limit[1] = + readl(context_icn.base + NODE_HIBW1_DDR_IN_1_LIMIT); + context_icn.hibw1_ddr_in_limit[2] = + readl(context_icn.base + NODE_HIBW1_DDR_IN_2_LIMIT); + + context_icn.hibw1_ddr_out_prio_reg = + readl(context_icn.base + NODE_HIBW1_DDR_OUT_0_PRIORITY); + + /* hibw2 */ + context_icn.hibw2_esram_in_pri[0] = + readl(context_icn.base + NODE_HIBW2_ESRAM_IN_0_PRIORITY); + context_icn.hibw2_esram_in_pri[1] = + readl(context_icn.base + NODE_HIBW2_ESRAM_IN_1_PRIORITY); + + context_icn.hibw2_esram_in0_arblimit[0] = + readl(context_icn.base + NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT); + context_icn.hibw2_esram_in0_arblimit[1] = + readl(context_icn.base + NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT); + context_icn.hibw2_esram_in0_arblimit[2] = + readl(context_icn.base + NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT); + + context_icn.hibw2_esram_in1_arblimit[0] = + readl(context_icn.base + NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT); + context_icn.hibw2_esram_in1_arblimit[1] = + readl(context_icn.base + NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT); + context_icn.hibw2_esram_in1_arblimit[2] = + readl(context_icn.base + NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT); + + context_icn.hibw2_ddr_in_prio[0] = + readl(context_icn.base + NODE_HIBW2_DDR_IN_0_PRIORITY); + context_icn.hibw2_ddr_in_prio[1] = + readl(context_icn.base + NODE_HIBW2_DDR_IN_1_PRIORITY); + context_icn.hibw2_ddr_in_prio[2] = + readl(context_icn.base + NODE_HIBW2_DDR_IN_2_PRIORITY); + context_icn.hibw2_ddr_in_prio[3] = + readl(context_icn.base + NODE_HIBW2_DDR_IN_3_PRIORITY); + + context_icn.hibw2_ddr_in_limit[0] = + readl(context_icn.base + NODE_HIBW2_DDR_IN_0_LIMIT); + context_icn.hibw2_ddr_in_limit[1] = + readl(context_icn.base + NODE_HIBW2_DDR_IN_1_LIMIT); + context_icn.hibw2_ddr_in_limit[2] = + readl(context_icn.base + NODE_HIBW2_DDR_IN_2_LIMIT); + context_icn.hibw2_ddr_in_limit[3] = + readl(context_icn.base + NODE_HIBW2_DDR_IN_3_LIMIT); + + context_icn.hibw2_ddr_out_prio_reg = + readl(context_icn.base + NODE_HIBW2_DDR_OUT_0_PRIORITY); + + /* ESRAM0 */ + context_icn.esram_in_prio[0] = + readl(context_icn.base + NODE_ESRAM0_IN_0_PRIORITY); + context_icn.esram_in_prio[1] = + readl(context_icn.base + NODE_ESRAM0_IN_1_PRIORITY); + context_icn.esram_in_prio[2] = + readl(context_icn.base + NODE_ESRAM0_IN_2_PRIORITY); + + context_icn.esram_in_lim[0] = + readl(context_icn.base + NODE_ESRAM0_IN_0_LIMIT); + context_icn.esram_in_lim[1] = + readl(context_icn.base + NODE_ESRAM0_IN_1_LIMIT); + context_icn.esram_in_lim[2] = + readl(context_icn.base + NODE_ESRAM0_IN_2_LIMIT); + + context_icn.esram_out_prio_reg = + readl(context_icn.base + NODE_ESRAM0_OUT_0_PRIORITY); + + /* ESRAM1-2 */ + context_icn.esram12_in_prio[0] = + readl(context_icn.base + NODE_ESRAM1_2_IN_0_PRIORITY); + context_icn.esram12_in_prio[1] = + readl(context_icn.base + NODE_ESRAM1_2_IN_1_PRIORITY); + context_icn.esram12_in_prio[2] = + readl(context_icn.base + NODE_ESRAM1_2_IN_2_PRIORITY); + + context_icn.esram12_in_arb_lim[0] = + readl(context_icn.base + NODE_ESRAM1_2_IN_0_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[1] = + readl(context_icn.base + NODE_ESRAM1_2_IN_1_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[2] = + readl(context_icn.base + NODE_ESRAM1_2_IN_2_ARB_1_LIMIT); + + context_icn.esram12_out_prio_reg = + readl(context_icn.base + NODE_ESRAM1_2_OUT_0_PRIORITY); + + /* ESRAM3-4 */ + context_icn.esram34_in_prio[0] = + readl(context_icn.base + NODE_ESRAM3_4_IN_0_PRIORITY); + context_icn.esram34_in_prio[1] = + readl(context_icn.base + NODE_ESRAM3_4_IN_1_PRIORITY); + context_icn.esram34_in_prio[2] = + readl(context_icn.base + NODE_ESRAM3_4_IN_2_PRIORITY); + + context_icn.esram34_in_arb_lim[0] = + readl(context_icn.base + NODE_ESRAM3_4_IN_0_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[1] = + readl(context_icn.base + NODE_ESRAM3_4_IN_1_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[2] = + readl(context_icn.base + NODE_ESRAM3_4_IN_2_ARB_1_LIMIT); + + context_icn.esram34_out_prio = + readl(context_icn.base + NODE_ESRAM3_4_OUT_0_PRIORITY); +} + +/* + * Restore ICN configuration registers + */ +void u5500_context_restore_icn(void) +{ + + /* hibw1 */ + writel(context_icn.hibw1_esram_in_pri[0], + context_icn.base + NODE_HIBW1_ESRAM_IN_0_PRIORITY); + writel(context_icn.hibw1_esram_in_pri[1], + context_icn.base + NODE_HIBW1_ESRAM_IN_1_PRIORITY); + + writel(context_icn.hibw1_esram_in0_arb[0], + context_icn.base + NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT); + writel(context_icn.hibw1_esram_in0_arb[1], + context_icn.base + NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT); + writel(context_icn.hibw1_esram_in0_arb[2], + context_icn.base + NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT); + + writel(context_icn.hibw1_esram_in1_arb[0], + context_icn.base + NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT); + writel(context_icn.hibw1_esram_in1_arb[1], + context_icn.base + NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT); + writel(context_icn.hibw1_esram_in1_arb[2], + context_icn.base + NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT); + + writel(context_icn.hibw1_ddr_in_prio[0], + context_icn.base + NODE_HIBW1_DDR_IN_0_PRIORITY); + writel(context_icn.hibw1_ddr_in_prio[1], + context_icn.base + NODE_HIBW1_DDR_IN_1_PRIORITY); + writel(context_icn.hibw1_ddr_in_prio[2], + context_icn.base + NODE_HIBW1_DDR_IN_2_PRIORITY); + + writel(context_icn.hibw1_ddr_in_limit[0], + context_icn.base + NODE_HIBW1_DDR_IN_0_LIMIT); + writel(context_icn.hibw1_ddr_in_limit[1], + context_icn.base + NODE_HIBW1_DDR_IN_1_LIMIT); + writel(context_icn.hibw1_ddr_in_limit[2], + context_icn.base + NODE_HIBW1_DDR_IN_2_LIMIT); + + writel(context_icn.hibw1_ddr_out_prio_reg, + context_icn.base + NODE_HIBW1_DDR_OUT_0_PRIORITY); + + /* hibw2 */ + writel(context_icn.hibw2_esram_in_pri[0], + context_icn.base + NODE_HIBW2_ESRAM_IN_0_PRIORITY); + writel(context_icn.hibw2_esram_in_pri[1], + context_icn.base + NODE_HIBW2_ESRAM_IN_1_PRIORITY); + + writel(context_icn.hibw2_esram_in0_arblimit[0], + context_icn.base + NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT); + writel(context_icn.hibw2_esram_in0_arblimit[1], + context_icn.base + NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT); + writel(context_icn.hibw2_esram_in0_arblimit[2], + context_icn.base + NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT); + + writel(context_icn.hibw2_esram_in1_arblimit[0], + context_icn.base + NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT); + writel(context_icn.hibw2_esram_in1_arblimit[1], + context_icn.base + NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT); + writel(context_icn.hibw2_esram_in1_arblimit[2], + context_icn.base + NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT); + + writel(context_icn.hibw2_ddr_in_prio[0], + context_icn.base + NODE_HIBW2_DDR_IN_0_PRIORITY); + writel(context_icn.hibw2_ddr_in_prio[1], + context_icn.base + NODE_HIBW2_DDR_IN_1_PRIORITY); + writel(context_icn.hibw2_ddr_in_prio[2], + context_icn.base + NODE_HIBW2_DDR_IN_2_PRIORITY); + writel(context_icn.hibw2_ddr_in_prio[3], + context_icn.base + NODE_HIBW2_DDR_IN_3_PRIORITY); + + writel(context_icn.hibw2_ddr_in_limit[0], + context_icn.base + NODE_HIBW2_DDR_IN_0_LIMIT); + writel(context_icn.hibw2_ddr_in_limit[1], + context_icn.base + NODE_HIBW2_DDR_IN_1_LIMIT); + writel(context_icn.hibw2_ddr_in_limit[2], + context_icn.base + NODE_HIBW2_DDR_IN_2_LIMIT); + writel(context_icn.hibw2_ddr_in_limit[3], + context_icn.base + NODE_HIBW2_DDR_IN_3_LIMIT); + + writel(context_icn.hibw2_ddr_out_prio_reg, + context_icn.base + NODE_HIBW2_DDR_OUT_0_PRIORITY); + + /* ESRAM0 */ + writel(context_icn.esram_in_prio[0], + context_icn.base + NODE_ESRAM0_IN_0_PRIORITY); + writel(context_icn.esram_in_prio[1], + context_icn.base + NODE_ESRAM0_IN_1_PRIORITY); + writel(context_icn.esram_in_prio[2], + context_icn.base + NODE_ESRAM0_IN_2_PRIORITY); + + writel(context_icn.esram_in_lim[0], + context_icn.base + NODE_ESRAM0_IN_0_LIMIT); + writel(context_icn.esram_in_lim[1], + context_icn.base + NODE_ESRAM0_IN_1_LIMIT); + writel(context_icn.esram_in_lim[2], + context_icn.base + NODE_ESRAM0_IN_2_LIMIT); + + writel(context_icn.esram_out_prio_reg, + context_icn.base + NODE_ESRAM0_OUT_0_PRIORITY); + + /* ESRAM1-2 */ + writel(context_icn.esram12_in_prio[0], + context_icn.base + NODE_ESRAM1_2_IN_0_PRIORITY); + writel(context_icn.esram12_in_prio[1], + context_icn.base + NODE_ESRAM1_2_IN_1_PRIORITY); + writel(context_icn.esram12_in_prio[2], + context_icn.base + NODE_ESRAM1_2_IN_2_PRIORITY); + + writel(context_icn.esram12_in_arb_lim[0], + context_icn.base + NODE_ESRAM1_2_IN_0_ARB_1_LIMIT); + writel(context_icn.esram12_in_arb_lim[1], + context_icn.base + NODE_ESRAM1_2_IN_1_ARB_1_LIMIT); + writel(context_icn.esram12_in_arb_lim[2], + context_icn.base + NODE_ESRAM1_2_IN_2_ARB_1_LIMIT); + + writel(context_icn.esram12_out_prio_reg, + context_icn.base + NODE_ESRAM1_2_OUT_0_PRIORITY); + + /* ESRAM3-4 */ + writel(context_icn.esram34_in_prio[0], + context_icn.base + NODE_ESRAM3_4_IN_0_PRIORITY); + writel(context_icn.esram34_in_prio[1], + context_icn.base + NODE_ESRAM3_4_IN_1_PRIORITY); + writel(context_icn.esram34_in_prio[2], + context_icn.base + NODE_ESRAM3_4_IN_2_PRIORITY); + + writel(context_icn.esram34_in_arb_lim[0], + context_icn.base + NODE_ESRAM3_4_IN_0_ARB_1_LIMIT); + writel(context_icn.esram34_in_arb_lim[1], + context_icn.base + NODE_ESRAM3_4_IN_1_ARB_1_LIMIT); + writel(context_icn.esram34_in_arb_lim[2], + context_icn.base + NODE_ESRAM3_4_IN_2_ARB_1_LIMIT); + + writel(context_icn.esram34_out_prio, + context_icn.base + NODE_ESRAM3_4_OUT_0_PRIORITY); + +} + +void u5500_context_init(void) +{ + context_icn.base = ioremap(U5500_ICN_BASE, SZ_8K); +} diff --git a/arch/arm/mach-ux500/pm/context-db8500.c b/arch/arm/mach-ux500/pm/context-db8500.c new file mode 100644 index 00000000000..3ba73e51a6d --- /dev/null +++ b/arch/arm/mach-ux500/pm/context-db8500.c @@ -0,0 +1,456 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * Author: Sundar Iyer for ST-Ericsson + * + */ + +#include <linux/io.h> + +#include <mach/hardware.h> +#include <mach/context.h> + +/* + * ST-Interconnect context + */ + +/* priority, bw limiter register offsets */ +#define NODE_HIBW1_ESRAM_IN_0_PRIORITY 0x00 +#define NODE_HIBW1_ESRAM_IN_1_PRIORITY 0x04 +#define NODE_HIBW1_ESRAM_IN_2_PRIORITY 0x08 +#define NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT 0x24 +#define NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT 0x28 +#define NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT 0x2C +#define NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT 0x30 +#define NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT 0x34 +#define NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT 0x38 +#define NODE_HIBW1_ESRAM_IN_2_ARB_1_LIMIT 0x3C +#define NODE_HIBW1_ESRAM_IN_2_ARB_2_LIMIT 0x40 +#define NODE_HIBW1_ESRAM_IN_2_ARB_3_LIMIT 0x44 +#define NODE_HIBW1_DDR_IN_0_PRIORITY 0x400 +#define NODE_HIBW1_DDR_IN_1_PRIORITY 0x404 +#define NODE_HIBW1_DDR_IN_2_PRIORITY 0x408 +#define NODE_HIBW1_DDR_IN_0_LIMIT 0x424 +#define NODE_HIBW1_DDR_IN_1_LIMIT 0x428 +#define NODE_HIBW1_DDR_IN_2_LIMIT 0x42C +#define NODE_HIBW1_DDR_OUT_0_PRIORITY 0x430 +#define NODE_HIBW2_ESRAM_IN_0_PRIORITY 0x800 +#define NODE_HIBW2_ESRAM_IN_1_PRIORITY 0x804 +#define NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT 0x818 +#define NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT 0x81C +#define NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT 0x820 +#define NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT 0x824 +#define NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT 0x828 +#define NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT 0x82C +#define NODE_HIBW2_DDR_IN_0_PRIORITY 0xC00 +#define NODE_HIBW2_DDR_IN_1_PRIORITY 0xC04 +#define NODE_HIBW2_DDR_IN_2_PRIORITY 0xC08 + +#define NODE_HIBW2_DDR_IN_0_LIMIT 0xC24 +#define NODE_HIBW2_DDR_IN_1_LIMIT 0xC28 +#define NODE_HIBW2_DDR_IN_2_LIMIT 0xC2C +#define NODE_HIBW2_DDR_OUT_0_PRIORITY 0xC30 + +/* + * Note the following addresses are presented in + * db8500 design spec v3.1 and v3.3, table 10. + * But their addresses are not the same as in the + * description. The addresses in the description + * of each registers are correct. + * NODE_HIBW2_DDR_IN_3_LIMIT is only present in v1. + * + * Faulty registers addresses in table 10: + * NODE_HIBW2_DDR_IN_2_LIMIT 0xC38 + * NODE_HIBW2_DDR_IN_3_LIMIT 0xC3C + * NODE_HIBW2_DDR_OUT_0_PRIORITY 0xC40 + */ + +#define NODE_ESRAM0_IN_0_PRIORITY 0x1000 +#define NODE_ESRAM0_IN_1_PRIORITY 0x1004 +#define NODE_ESRAM0_IN_2_PRIORITY 0x1008 +#define NODE_ESRAM0_IN_3_PRIORITY 0x100C +#define NODE_ESRAM0_IN_0_LIMIT 0x1030 +#define NODE_ESRAM0_IN_1_LIMIT 0x1034 +#define NODE_ESRAM0_IN_2_LIMIT 0x1038 +#define NODE_ESRAM0_IN_3_LIMIT 0x103C +/* common */ +#define NODE_ESRAM1_2_IN_0_PRIORITY 0x1400 +#define NODE_ESRAM1_2_IN_1_PRIORITY 0x1404 +#define NODE_ESRAM1_2_IN_2_PRIORITY 0x1408 +#define NODE_ESRAM1_2_IN_3_PRIORITY 0x140C +#define NODE_ESRAM1_2_IN_0_ARB_1_LIMIT 0x1430 +#define NODE_ESRAM1_2_IN_0_ARB_2_LIMIT 0x1434 +#define NODE_ESRAM1_2_IN_1_ARB_1_LIMIT 0x1438 +#define NODE_ESRAM1_2_IN_1_ARB_2_LIMIT 0x143C +#define NODE_ESRAM1_2_IN_2_ARB_1_LIMIT 0x1440 +#define NODE_ESRAM1_2_IN_2_ARB_2_LIMIT 0x1444 +#define NODE_ESRAM1_2_IN_3_ARB_1_LIMIT 0x1448 +#define NODE_ESRAM1_2_IN_3_ARB_2_LIMIT 0x144C + +#define NODE_ESRAM3_4_IN_0_PRIORITY 0x1800 +#define NODE_ESRAM3_4_IN_1_PRIORITY 0x1804 +#define NODE_ESRAM3_4_IN_2_PRIORITY 0x1808 +#define NODE_ESRAM3_4_IN_3_PRIORITY 0x180C +#define NODE_ESRAM3_4_IN_0_ARB_1_LIMIT 0x1830 +#define NODE_ESRAM3_4_IN_0_ARB_2_LIMIT 0x1834 +#define NODE_ESRAM3_4_IN_1_ARB_1_LIMIT 0x1838 +#define NODE_ESRAM3_4_IN_1_ARB_2_LIMIT 0x183C +#define NODE_ESRAM3_4_IN_2_ARB_1_LIMIT 0x1840 +#define NODE_ESRAM3_4_IN_2_ARB_2_LIMIT 0x1844 +#define NODE_ESRAM3_4_IN_3_ARB_1_LIMIT 0x1848 +#define NODE_ESRAM3_4_IN_3_ARB_2_LIMIT 0x184C + +static struct { + void __iomem *base; + u32 hibw1_esram_in_pri[3]; + u32 hibw1_esram_in0_arb[3]; + u32 hibw1_esram_in1_arb[3]; + u32 hibw1_esram_in2_arb[3]; + u32 hibw1_ddr_in_prio[3]; + u32 hibw1_ddr_in_limit[3]; + u32 hibw1_ddr_out_prio; + + /* HiBw2 node registers */ + u32 hibw2_esram_in_pri[2]; + u32 hibw2_esram_in0_arblimit[3]; + u32 hibw2_esram_in1_arblimit[3]; + u32 hibw2_ddr_in_prio[4]; + u32 hibw2_ddr_in_limit[4]; + u32 hibw2_ddr_out_prio; + + /* ESRAM node registers */ + u32 esram_in_prio[4]; + u32 esram_in_lim[4]; + u32 esram0_in_prio[4]; + u32 esram0_in_lim[4]; + u32 esram12_in_prio[4]; + u32 esram12_in_arb_lim[8]; + u32 esram34_in_prio[4]; + u32 esram34_in_arb_lim[8]; +} context_icn; + +/** + * u8500_context_save_icn() - save ICN context + * + */ +void u8500_context_save_icn(void) +{ + void __iomem *b = context_icn.base; + + context_icn.hibw1_esram_in_pri[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_0_PRIORITY); + context_icn.hibw1_esram_in_pri[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_1_PRIORITY); + context_icn.hibw1_esram_in_pri[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_2_PRIORITY); + + context_icn.hibw1_esram_in0_arb[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT); + context_icn.hibw1_esram_in0_arb[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT); + context_icn.hibw1_esram_in0_arb[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT); + + context_icn.hibw1_esram_in1_arb[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT); + context_icn.hibw1_esram_in1_arb[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT); + context_icn.hibw1_esram_in1_arb[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT); + + context_icn.hibw1_esram_in2_arb[0] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_2_ARB_1_LIMIT); + context_icn.hibw1_esram_in2_arb[1] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_2_ARB_2_LIMIT); + context_icn.hibw1_esram_in2_arb[2] = + readl_relaxed(b + NODE_HIBW1_ESRAM_IN_2_ARB_3_LIMIT); + + context_icn.hibw1_ddr_in_prio[0] = + readl_relaxed(b + NODE_HIBW1_DDR_IN_0_PRIORITY); + context_icn.hibw1_ddr_in_prio[1] = + readl_relaxed(b + NODE_HIBW1_DDR_IN_1_PRIORITY); + context_icn.hibw1_ddr_in_prio[2] = + readl_relaxed(b + NODE_HIBW1_DDR_IN_2_PRIORITY); + + context_icn.hibw1_ddr_in_limit[0] = + readl_relaxed(b + NODE_HIBW1_DDR_IN_0_LIMIT); + context_icn.hibw1_ddr_in_limit[1] = + readl_relaxed(b + NODE_HIBW1_DDR_IN_1_LIMIT); + context_icn.hibw1_ddr_in_limit[2] = + readl_relaxed(b + NODE_HIBW1_DDR_IN_2_LIMIT); + + context_icn.hibw1_ddr_out_prio = + readl_relaxed(b + NODE_HIBW1_DDR_OUT_0_PRIORITY); + + context_icn.hibw2_esram_in_pri[0] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_0_PRIORITY); + context_icn.hibw2_esram_in_pri[1] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_1_PRIORITY); + + context_icn.hibw2_esram_in0_arblimit[0] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT); + context_icn.hibw2_esram_in0_arblimit[1] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT); + context_icn.hibw2_esram_in0_arblimit[2] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT); + + context_icn.hibw2_esram_in1_arblimit[0] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT); + context_icn.hibw2_esram_in1_arblimit[1] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT); + context_icn.hibw2_esram_in1_arblimit[2] = + readl_relaxed(b + NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT); + + context_icn.hibw2_ddr_in_prio[0] = + readl_relaxed(b + NODE_HIBW2_DDR_IN_0_PRIORITY); + context_icn.hibw2_ddr_in_prio[1] = + readl_relaxed(b + NODE_HIBW2_DDR_IN_1_PRIORITY); + context_icn.hibw2_ddr_in_prio[2] = + readl_relaxed(b + NODE_HIBW2_DDR_IN_2_PRIORITY); + + context_icn.hibw2_ddr_in_limit[0] = + readl_relaxed(b + NODE_HIBW2_DDR_IN_0_LIMIT); + context_icn.hibw2_ddr_in_limit[1] = + readl_relaxed(b + NODE_HIBW2_DDR_IN_1_LIMIT); + + context_icn.hibw2_ddr_in_limit[2] = + readl_relaxed(b + NODE_HIBW2_DDR_IN_2_LIMIT); + + context_icn.hibw2_ddr_out_prio = + readl_relaxed(b + NODE_HIBW2_DDR_OUT_0_PRIORITY); + + context_icn.esram0_in_prio[0] = + readl_relaxed(b + NODE_ESRAM0_IN_0_PRIORITY); + context_icn.esram0_in_prio[1] = + readl_relaxed(b + NODE_ESRAM0_IN_1_PRIORITY); + context_icn.esram0_in_prio[2] = + readl_relaxed(b + NODE_ESRAM0_IN_2_PRIORITY); + context_icn.esram0_in_prio[3] = + readl_relaxed(b + NODE_ESRAM0_IN_3_PRIORITY); + + context_icn.esram0_in_lim[0] = + readl_relaxed(b + NODE_ESRAM0_IN_0_LIMIT); + context_icn.esram0_in_lim[1] = + readl_relaxed(b + NODE_ESRAM0_IN_1_LIMIT); + context_icn.esram0_in_lim[2] = + readl_relaxed(b + NODE_ESRAM0_IN_2_LIMIT); + context_icn.esram0_in_lim[3] = + readl_relaxed(b + NODE_ESRAM0_IN_3_LIMIT); + + context_icn.esram12_in_prio[0] = + readl_relaxed(b + NODE_ESRAM1_2_IN_0_PRIORITY); + context_icn.esram12_in_prio[1] = + readl_relaxed(b + NODE_ESRAM1_2_IN_1_PRIORITY); + context_icn.esram12_in_prio[2] = + readl_relaxed(b + NODE_ESRAM1_2_IN_2_PRIORITY); + context_icn.esram12_in_prio[3] = + readl_relaxed(b + NODE_ESRAM1_2_IN_3_PRIORITY); + + context_icn.esram12_in_arb_lim[0] = + readl_relaxed(b + NODE_ESRAM1_2_IN_0_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[1] = + readl_relaxed(b + NODE_ESRAM1_2_IN_0_ARB_2_LIMIT); + context_icn.esram12_in_arb_lim[2] = + readl_relaxed(b + NODE_ESRAM1_2_IN_1_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[3] = + readl_relaxed(b + NODE_ESRAM1_2_IN_1_ARB_2_LIMIT); + context_icn.esram12_in_arb_lim[4] = + readl_relaxed(b + NODE_ESRAM1_2_IN_2_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[5] = + readl_relaxed(b + NODE_ESRAM1_2_IN_2_ARB_2_LIMIT); + context_icn.esram12_in_arb_lim[6] = + readl_relaxed(b + NODE_ESRAM1_2_IN_3_ARB_1_LIMIT); + context_icn.esram12_in_arb_lim[7] = + readl_relaxed(b + NODE_ESRAM1_2_IN_3_ARB_2_LIMIT); + + context_icn.esram34_in_prio[0] = + readl_relaxed(b + NODE_ESRAM3_4_IN_0_PRIORITY); + context_icn.esram34_in_prio[1] = + readl_relaxed(b + NODE_ESRAM3_4_IN_1_PRIORITY); + context_icn.esram34_in_prio[2] = + readl_relaxed(b + NODE_ESRAM3_4_IN_2_PRIORITY); + context_icn.esram34_in_prio[3] = + readl_relaxed(b + NODE_ESRAM3_4_IN_3_PRIORITY); + + context_icn.esram34_in_arb_lim[0] = + readl_relaxed(b + NODE_ESRAM3_4_IN_0_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[1] = + readl_relaxed(b + NODE_ESRAM3_4_IN_0_ARB_2_LIMIT); + context_icn.esram34_in_arb_lim[2] = + readl_relaxed(b + NODE_ESRAM3_4_IN_1_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[3] = + readl_relaxed(b + NODE_ESRAM3_4_IN_1_ARB_2_LIMIT); + context_icn.esram34_in_arb_lim[4] = + readl_relaxed(b + NODE_ESRAM3_4_IN_2_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[5] = + readl_relaxed(b + NODE_ESRAM3_4_IN_2_ARB_2_LIMIT); + context_icn.esram34_in_arb_lim[6] = + readl_relaxed(b + NODE_ESRAM3_4_IN_3_ARB_1_LIMIT); + context_icn.esram34_in_arb_lim[7] = + readl_relaxed(b + NODE_ESRAM3_4_IN_3_ARB_2_LIMIT); +} + +/** + * u8500_context_restore_icn() - restore ICN context + * + */ +void u8500_context_restore_icn(void) +{ + void __iomem *b = context_icn.base; + + writel_relaxed(context_icn.hibw1_esram_in_pri[0], + b + NODE_HIBW1_ESRAM_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw1_esram_in_pri[1], + b + NODE_HIBW1_ESRAM_IN_1_PRIORITY); + writel_relaxed(context_icn.hibw1_esram_in_pri[2], + b + NODE_HIBW1_ESRAM_IN_2_PRIORITY); + + writel_relaxed(context_icn.hibw1_esram_in0_arb[0], + b + NODE_HIBW1_ESRAM_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in0_arb[1], + b + NODE_HIBW1_ESRAM_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in0_arb[2], + b + NODE_HIBW1_ESRAM_IN_0_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw1_esram_in1_arb[0], + b + NODE_HIBW1_ESRAM_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in1_arb[1], + b + NODE_HIBW1_ESRAM_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in1_arb[2], + b + NODE_HIBW1_ESRAM_IN_1_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw1_esram_in2_arb[0], + b + NODE_HIBW1_ESRAM_IN_2_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in2_arb[1], + b + NODE_HIBW1_ESRAM_IN_2_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw1_esram_in2_arb[2], + b + NODE_HIBW1_ESRAM_IN_2_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw1_ddr_in_prio[0], + b + NODE_HIBW1_DDR_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw1_ddr_in_prio[1], + b + NODE_HIBW1_DDR_IN_1_PRIORITY); + writel_relaxed(context_icn.hibw1_ddr_in_prio[2], + b + NODE_HIBW1_DDR_IN_2_PRIORITY); + + writel_relaxed(context_icn.hibw1_ddr_in_limit[0], + b + NODE_HIBW1_DDR_IN_0_LIMIT); + writel_relaxed(context_icn.hibw1_ddr_in_limit[1], + b + NODE_HIBW1_DDR_IN_1_LIMIT); + writel_relaxed(context_icn.hibw1_ddr_in_limit[2], + b + NODE_HIBW1_DDR_IN_2_LIMIT); + + writel_relaxed(context_icn.hibw1_ddr_out_prio, + b + NODE_HIBW1_DDR_OUT_0_PRIORITY); + + writel_relaxed(context_icn.hibw2_esram_in_pri[0], + b + NODE_HIBW2_ESRAM_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw2_esram_in_pri[1], + b + NODE_HIBW2_ESRAM_IN_1_PRIORITY); + + writel_relaxed(context_icn.hibw2_esram_in0_arblimit[0], + b + NODE_HIBW2_ESRAM_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in0_arblimit[1], + b + NODE_HIBW2_ESRAM_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in0_arblimit[2], + b + NODE_HIBW2_ESRAM_IN_0_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw2_esram_in1_arblimit[0], + b + NODE_HIBW2_ESRAM_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in1_arblimit[1], + b + NODE_HIBW2_ESRAM_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.hibw2_esram_in1_arblimit[2], + b + NODE_HIBW2_ESRAM_IN_1_ARB_3_LIMIT); + + writel_relaxed(context_icn.hibw2_ddr_in_prio[0], + b + NODE_HIBW2_DDR_IN_0_PRIORITY); + writel_relaxed(context_icn.hibw2_ddr_in_prio[1], + b + NODE_HIBW2_DDR_IN_1_PRIORITY); + writel_relaxed(context_icn.hibw2_ddr_in_prio[2], + b + NODE_HIBW2_DDR_IN_2_PRIORITY); + writel_relaxed(context_icn.hibw2_ddr_in_limit[0], + b + NODE_HIBW2_DDR_IN_0_LIMIT); + writel_relaxed(context_icn.hibw2_ddr_in_limit[1], + b + NODE_HIBW2_DDR_IN_1_LIMIT); + writel_relaxed(context_icn.hibw2_ddr_in_limit[2], + b + NODE_HIBW2_DDR_IN_2_LIMIT); + writel_relaxed(context_icn.hibw2_ddr_out_prio, + b + NODE_HIBW2_DDR_OUT_0_PRIORITY); + + writel_relaxed(context_icn.esram0_in_prio[0], + b + NODE_ESRAM0_IN_0_PRIORITY); + writel_relaxed(context_icn.esram0_in_prio[1], + b + NODE_ESRAM0_IN_1_PRIORITY); + writel_relaxed(context_icn.esram0_in_prio[2], + b + NODE_ESRAM0_IN_2_PRIORITY); + writel_relaxed(context_icn.esram0_in_prio[3], + b + NODE_ESRAM0_IN_3_PRIORITY); + + writel_relaxed(context_icn.esram0_in_lim[0], + b + NODE_ESRAM0_IN_0_LIMIT); + writel_relaxed(context_icn.esram0_in_lim[1], + b + NODE_ESRAM0_IN_1_LIMIT); + writel_relaxed(context_icn.esram0_in_lim[2], + b + NODE_ESRAM0_IN_2_LIMIT); + writel_relaxed(context_icn.esram0_in_lim[3], + b + NODE_ESRAM0_IN_3_LIMIT); + + writel_relaxed(context_icn.esram12_in_prio[0], + b + NODE_ESRAM1_2_IN_0_PRIORITY); + writel_relaxed(context_icn.esram12_in_prio[1], + b + NODE_ESRAM1_2_IN_1_PRIORITY); + writel_relaxed(context_icn.esram12_in_prio[2], + b + NODE_ESRAM1_2_IN_2_PRIORITY); + writel_relaxed(context_icn.esram12_in_prio[3], + b + NODE_ESRAM1_2_IN_3_PRIORITY); + + writel_relaxed(context_icn.esram12_in_arb_lim[0], + b + NODE_ESRAM1_2_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[1], + b + NODE_ESRAM1_2_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[2], + b + NODE_ESRAM1_2_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[3], + b + NODE_ESRAM1_2_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[4], + b + NODE_ESRAM1_2_IN_2_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[5], + b + NODE_ESRAM1_2_IN_2_ARB_2_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[6], + b + NODE_ESRAM1_2_IN_3_ARB_1_LIMIT); + writel_relaxed(context_icn.esram12_in_arb_lim[7], + b + NODE_ESRAM1_2_IN_3_ARB_2_LIMIT); + + writel_relaxed(context_icn.esram34_in_prio[0], + b + NODE_ESRAM3_4_IN_0_PRIORITY); + writel_relaxed(context_icn.esram34_in_prio[1], + b + NODE_ESRAM3_4_IN_1_PRIORITY); + writel_relaxed(context_icn.esram34_in_prio[2], + b + NODE_ESRAM3_4_IN_2_PRIORITY); + writel_relaxed(context_icn.esram34_in_prio[3], + b + NODE_ESRAM3_4_IN_3_PRIORITY); + + writel_relaxed(context_icn.esram34_in_arb_lim[0], + b + NODE_ESRAM3_4_IN_0_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[1], + b + NODE_ESRAM3_4_IN_0_ARB_2_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[2], + b + NODE_ESRAM3_4_IN_1_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[3], + b + NODE_ESRAM3_4_IN_1_ARB_2_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[4], + b + NODE_ESRAM3_4_IN_2_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[5], + b + NODE_ESRAM3_4_IN_2_ARB_2_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[6], + b + NODE_ESRAM3_4_IN_3_ARB_1_LIMIT); + writel_relaxed(context_icn.esram34_in_arb_lim[7], + b + NODE_ESRAM3_4_IN_3_ARB_2_LIMIT); +} + +void u8500_context_init(void) +{ + context_icn.base = ioremap(U8500_ICN_BASE, SZ_8K); +} diff --git a/arch/arm/mach-ux500/pm/context.c b/arch/arm/mach-ux500/pm/context.c new file mode 100644 index 00000000000..b22e3ad7fd1 --- /dev/null +++ b/arch/arm/mach-ux500/pm/context.c @@ -0,0 +1,977 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com>, + * Rickard Andersson <rickard.andersson@stericsson.com>, + * Jonas Aaberg <jonas.aberg@stericsson.com>, + * Sundar Iyer for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/smp.h> +#include <linux/percpu.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/notifier.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/gpio/nomadik.h> + +#include <mach/hardware.h> +#include <mach/irqs.h> +#include <mach/pm.h> +#include <mach/context.h> + +#include <asm/hardware/gic.h> +#include <asm/smp_twd.h> + +#include "scu.h" +#include "../product.h" + +#define GPIO_NUM_BANKS 9 +#define GPIO_NUM_SAVE_REGISTERS 7 + +/* + * TODO: + * - Use the "UX500*"-macros instead where possible + */ + +#define U8500_BACKUPRAM_SIZE SZ_64K + +#define U8500_PUBLIC_BOOT_ROM_BASE (U8500_BOOT_ROM_BASE + 0x17000) +#define U5500_PUBLIC_BOOT_ROM_BASE (U5500_BOOT_ROM_BASE + 0x18000) + +/* + * Special dedicated addresses in backup RAM. The 5500 addresses are identical + * to the 8500 ones. + */ +#define U8500_EXT_RAM_LOC_BACKUPRAM_ADDR 0x80151FDC +#define U8500_CPU0_CP15_CR_BACKUPRAM_ADDR 0x80151F80 +#define U8500_CPU1_CP15_CR_BACKUPRAM_ADDR 0x80151FA0 + +#define U8500_CPU0_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR 0x80151FD8 +#define U8500_CPU1_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR 0x80151FE0 + +#define GIC_DIST_ENABLE_NS 0x0 + +/* 32 interrupts fits in 4 bytes */ +#define GIC_DIST_ENABLE_SET_COMMON_NUM ((DBX500_NR_INTERNAL_IRQS - \ + IRQ_SHPI_START) / 32) +#define GIC_DIST_ENABLE_SET_CPU_NUM (IRQ_SHPI_START / 32) +#define GIC_DIST_ENABLE_SET_SPI0 GIC_DIST_ENABLE_SET +#define GIC_DIST_ENABLE_SET_SPI32 (GIC_DIST_ENABLE_SET + IRQ_SHPI_START / 8) + +#define GIC_DIST_ENABLE_CLEAR_0 GIC_DIST_ENABLE_CLEAR +#define GIC_DIST_ENABLE_CLEAR_32 (GIC_DIST_ENABLE_CLEAR + 4) +#define GIC_DIST_ENABLE_CLEAR_64 (GIC_DIST_ENABLE_CLEAR + 8) +#define GIC_DIST_ENABLE_CLEAR_96 (GIC_DIST_ENABLE_CLEAR + 12) +#define GIC_DIST_ENABLE_CLEAR_128 (GIC_DIST_ENABLE_CLEAR + 16) + +#define GIC_DIST_PRI_COMMON_NUM ((DBX500_NR_INTERNAL_IRQS - IRQ_SHPI_START) / 4) +#define GIC_DIST_PRI_CPU_NUM (IRQ_SHPI_START / 4) +#define GIC_DIST_PRI_SPI0 GIC_DIST_PRI +#define GIC_DIST_PRI_SPI32 (GIC_DIST_PRI + IRQ_SHPI_START) + +#define GIC_DIST_SPI_TARGET_COMMON_NUM ((DBX500_NR_INTERNAL_IRQS - \ + IRQ_SHPI_START) / 4) +#define GIC_DIST_SPI_TARGET_CPU_NUM (IRQ_SHPI_START / 4) +#define GIC_DIST_SPI_TARGET_SPI0 GIC_DIST_TARGET +#define GIC_DIST_SPI_TARGET_SPI32 (GIC_DIST_TARGET + IRQ_SHPI_START) + +/* 16 interrupts per 4 bytes */ +#define GIC_DIST_CONFIG_COMMON_NUM ((DBX500_NR_INTERNAL_IRQS - IRQ_SHPI_START) \ + / 16) +#define GIC_DIST_CONFIG_CPU_NUM (IRQ_SHPI_START / 16) +#define GIC_DIST_CONFIG_SPI0 GIC_DIST_CONFIG +#define GIC_DIST_CONFIG_SPI32 (GIC_DIST_CONFIG + IRQ_SHPI_START / 4) + +/* TODO! Move STM reg offsets to suitable place */ +#define STM_CR_OFFSET 0x00 +#define STM_MMC_OFFSET 0x08 +#define STM_TER_OFFSET 0x10 + +#define TPIU_PORT_SIZE 0x4 +#define TPIU_TRIGGER_COUNTER 0x104 +#define TPIU_TRIGGER_MULTIPLIER 0x108 +#define TPIU_CURRENT_TEST_PATTERN 0x204 +#define TPIU_TEST_PATTERN_REPEAT 0x208 +#define TPIU_FORMATTER 0x304 +#define TPIU_FORMATTER_SYNC 0x308 +#define TPIU_LOCK_ACCESS_REGISTER 0xFB0 + +#define TPIU_UNLOCK_CODE 0xc5acce55 + +#define SCU_FILTER_STARTADDR 0x40 +#define SCU_FILTER_ENDADDR 0x44 +#define SCU_ACCESS_CTRL_SAC 0x50 + +/* + * Periph clock cluster context + */ +#define PRCC_BCK_EN 0x00 +#define PRCC_KCK_EN 0x08 +#define PRCC_BCK_STATUS 0x10 +#define PRCC_KCK_STATUS 0x14 + +/* The context of the Trace Port Interface Unit (TPIU) */ +static struct { + void __iomem *base; + u32 port_size; + u32 trigger_counter; + u32 trigger_multiplier; + u32 current_test_pattern; + u32 test_pattern_repeat; + u32 formatter; + u32 formatter_sync; +} context_tpiu; + +static struct { + void __iomem *base; + u32 cr; + u32 mmc; + u32 ter; +} context_stm_ape; + +struct context_gic_cpu { + void __iomem *base; + u32 ctrl; + u32 primask; + u32 binpoint; +}; +static DEFINE_PER_CPU(struct context_gic_cpu, context_gic_cpu); + +static struct { + void __iomem *base; + u32 ns; + u32 enable_set[GIC_DIST_ENABLE_SET_COMMON_NUM]; /* IRQ 32 to 160 */ + u32 priority_level[GIC_DIST_PRI_COMMON_NUM]; + u32 spi_target[GIC_DIST_SPI_TARGET_COMMON_NUM]; + u32 config[GIC_DIST_CONFIG_COMMON_NUM]; +} context_gic_dist_common; + +struct context_gic_dist_cpu { + void __iomem *base; + u32 enable_set[GIC_DIST_ENABLE_SET_CPU_NUM]; /* IRQ 0 to 31 */ + u32 priority_level[GIC_DIST_PRI_CPU_NUM]; + u32 spi_target[GIC_DIST_SPI_TARGET_CPU_NUM]; + u32 config[GIC_DIST_CONFIG_CPU_NUM]; +}; +static DEFINE_PER_CPU(struct context_gic_dist_cpu, context_gic_dist_cpu); + +static struct { + void __iomem *base; + u32 ctrl; + u32 cpu_pwrstatus; + u32 inv_all_nonsecure; + u32 filter_start_addr; + u32 filter_end_addr; + u32 access_ctrl_sac; +} context_scu; + +#define UX500_NR_PRCC_BANKS 5 +static struct { + void __iomem *base; + struct clk *clk; + u32 bus_clk; + u32 kern_clk; +} context_prcc[UX500_NR_PRCC_BANKS]; + +static u32 backup_sram_storage[NR_CPUS] = { + IO_ADDRESS(U8500_CPU0_CP15_CR_BACKUPRAM_ADDR), + IO_ADDRESS(U8500_CPU1_CP15_CR_BACKUPRAM_ADDR), +}; + +static u32 gpio_bankaddr[GPIO_NUM_BANKS] = {IO_ADDRESS(U8500_GPIOBANK0_BASE), + IO_ADDRESS(U8500_GPIOBANK1_BASE), + IO_ADDRESS(U8500_GPIOBANK2_BASE), + IO_ADDRESS(U8500_GPIOBANK3_BASE), + IO_ADDRESS(U8500_GPIOBANK4_BASE), + IO_ADDRESS(U8500_GPIOBANK5_BASE), + IO_ADDRESS(U8500_GPIOBANK6_BASE), + IO_ADDRESS(U8500_GPIOBANK7_BASE), + IO_ADDRESS(U8500_GPIOBANK8_BASE) +}; + +static u32 gpio_save[GPIO_NUM_BANKS][GPIO_NUM_SAVE_REGISTERS]; + +/* + * Stacks and stack pointers + */ +static DEFINE_PER_CPU(u32[128], varm_registers_backup_stack); +static DEFINE_PER_CPU(u32 *, varm_registers_pointer); + +static DEFINE_PER_CPU(u32[128], varm_cp15_backup_stack); +static DEFINE_PER_CPU(u32 *, varm_cp15_pointer); + + +static ATOMIC_NOTIFIER_HEAD(context_ape_notifier_list); +static ATOMIC_NOTIFIER_HEAD(context_arm_notifier_list); + +/* + * Register a simple callback for handling vape context save/restore + */ +int context_ape_notifier_register(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&context_ape_notifier_list, nb); +} +EXPORT_SYMBOL(context_ape_notifier_register); + +/* + * Remove a previously registered callback + */ +int context_ape_notifier_unregister(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&context_ape_notifier_list, + nb); +} +EXPORT_SYMBOL(context_ape_notifier_unregister); + +/* + * Register a simple callback for handling varm context save/restore + */ +int context_arm_notifier_register(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&context_arm_notifier_list, nb); +} +EXPORT_SYMBOL(context_arm_notifier_register); + +/* + * Remove a previously registered callback + */ +int context_arm_notifier_unregister(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&context_arm_notifier_list, + nb); +} +EXPORT_SYMBOL(context_arm_notifier_unregister); + +static void save_prcc(void) +{ + int i; + + for (i = 0; i < UX500_NR_PRCC_BANKS; i++) { + clk_enable(context_prcc[i].clk); + + context_prcc[i].bus_clk = + readl(context_prcc[i].base + PRCC_BCK_STATUS); + context_prcc[i].kern_clk = + readl(context_prcc[i].base + PRCC_KCK_STATUS); + + clk_disable(context_prcc[i].clk); + } +} + +static void restore_prcc(void) +{ + int i; + + for (i = 0; i < UX500_NR_PRCC_BANKS; i++) { + clk_enable(context_prcc[i].clk); + + writel(context_prcc[i].bus_clk, + context_prcc[i].base + PRCC_BCK_EN); + writel(context_prcc[i].kern_clk, + context_prcc[i].base + PRCC_KCK_EN); + + clk_disable(context_prcc[i].clk); + } +} + +static void save_stm_ape(void) +{ + /* + * TODO: Check with PRCMU developers how STM is handled by PRCMU + * firmware. According to DB5500 design spec there is a "flush" + * mechanism supposed to be used by the PRCMU before power down, + * PRCMU fw might save/restore the following three registers + * at the same time. + */ + context_stm_ape.cr = readl(context_stm_ape.base + + STM_CR_OFFSET); + context_stm_ape.mmc = readl(context_stm_ape.base + + STM_MMC_OFFSET); + context_stm_ape.ter = readl(context_stm_ape.base + + STM_TER_OFFSET); +} + +static void restore_stm_ape(void) +{ + writel(context_stm_ape.ter, + context_stm_ape.base + STM_TER_OFFSET); + writel(context_stm_ape.mmc, + context_stm_ape.base + STM_MMC_OFFSET); + writel(context_stm_ape.cr, + context_stm_ape.base + STM_CR_OFFSET); +} + +static bool inline tpiu_clocked(void) +{ + return ux500_jtag_enabled(); +} + +/* + * Save the context of the Trace Port Interface Unit (TPIU). + * Saving/restoring is needed for the PTM tracing to work together + * with the sleep states ApSleep and ApDeepSleep. + */ +static void save_tpiu(void) +{ + if (!tpiu_clocked()) + return; + + context_tpiu.port_size = readl(context_tpiu.base + + TPIU_PORT_SIZE); + context_tpiu.trigger_counter = readl(context_tpiu.base + + TPIU_TRIGGER_COUNTER); + context_tpiu.trigger_multiplier = readl(context_tpiu.base + + TPIU_TRIGGER_MULTIPLIER); + context_tpiu.current_test_pattern = readl(context_tpiu.base + + TPIU_CURRENT_TEST_PATTERN); + context_tpiu.test_pattern_repeat = readl(context_tpiu.base + + TPIU_TEST_PATTERN_REPEAT); + context_tpiu.formatter = readl(context_tpiu.base + + TPIU_FORMATTER); + context_tpiu.formatter_sync = readl(context_tpiu.base + + TPIU_FORMATTER_SYNC); +} + +/* + * Restore the context of the Trace Port Interface Unit (TPIU). + * Saving/restoring is needed for the PTM tracing to work together + * with the sleep states ApSleep and ApDeepSleep. + */ +static void restore_tpiu(void) +{ + if (!tpiu_clocked()) + return; + + writel(TPIU_UNLOCK_CODE, + context_tpiu.base + TPIU_LOCK_ACCESS_REGISTER); + + writel(context_tpiu.port_size, + context_tpiu.base + TPIU_PORT_SIZE); + writel(context_tpiu.trigger_counter, + context_tpiu.base + TPIU_TRIGGER_COUNTER); + writel(context_tpiu.trigger_multiplier, + context_tpiu.base + TPIU_TRIGGER_MULTIPLIER); + writel(context_tpiu.current_test_pattern, + context_tpiu.base + TPIU_CURRENT_TEST_PATTERN); + writel(context_tpiu.test_pattern_repeat, + context_tpiu.base + TPIU_TEST_PATTERN_REPEAT); + writel(context_tpiu.formatter, + context_tpiu.base + TPIU_FORMATTER); + writel(context_tpiu.formatter_sync, + context_tpiu.base + TPIU_FORMATTER_SYNC); +} + +/* + * Save GIC CPU IF registers + * + * This is per cpu so it needs to be called for each one. + */ + +static void save_gic_if_cpu(struct context_gic_cpu *c_gic_cpu) +{ + c_gic_cpu->ctrl = readl_relaxed(c_gic_cpu->base + GIC_CPU_CTRL); + c_gic_cpu->primask = readl_relaxed(c_gic_cpu->base + GIC_CPU_PRIMASK); + c_gic_cpu->binpoint = readl_relaxed(c_gic_cpu->base + GIC_CPU_BINPOINT); +} + +/* + * Restore GIC CPU IF registers + * + * This is per cpu so it needs to be called for each one. + */ +static void restore_gic_if_cpu(struct context_gic_cpu *c_gic_cpu) +{ + writel_relaxed(c_gic_cpu->ctrl, c_gic_cpu->base + GIC_CPU_CTRL); + writel_relaxed(c_gic_cpu->primask, c_gic_cpu->base + GIC_CPU_PRIMASK); + writel_relaxed(c_gic_cpu->binpoint, c_gic_cpu->base + GIC_CPU_BINPOINT); +} + +/* + * Save GIC Distributor Common registers + * + * This context is common. Only one CPU needs to call. + * + * Save SPI (Shared Peripheral Interrupt) settings, IRQ 32-159. + */ + +static void save_gic_dist_common(void) +{ + int i; + + context_gic_dist_common.ns = readl_relaxed(context_gic_dist_common.base + + GIC_DIST_ENABLE_NS); + + for (i = 0; i < GIC_DIST_ENABLE_SET_COMMON_NUM; i++) + context_gic_dist_common.enable_set[i] = + readl_relaxed(context_gic_dist_common.base + + GIC_DIST_ENABLE_SET_SPI32 + i * 4); + + for (i = 0; i < GIC_DIST_PRI_COMMON_NUM; i++) + context_gic_dist_common.priority_level[i] = + readl_relaxed(context_gic_dist_common.base + + GIC_DIST_PRI_SPI32 + i * 4); + + for (i = 0; i < GIC_DIST_SPI_TARGET_COMMON_NUM; i++) + context_gic_dist_common.spi_target[i] = + readl_relaxed(context_gic_dist_common.base + + GIC_DIST_SPI_TARGET_SPI32 + i * 4); + + for (i = 0; i < GIC_DIST_CONFIG_COMMON_NUM; i++) + context_gic_dist_common.config[i] = + readl_relaxed(context_gic_dist_common.base + + GIC_DIST_CONFIG_SPI32 + i * 4); +} + +/* + * Restore GIC Distributor Common registers + * + * This context is common. Only one CPU needs to call. + * + * Save SPI (Shared Peripheral Interrupt) settings, IRQ 32-159. + */ +static void restore_gic_dist_common(void) +{ + + int i; + + for (i = 0; i < GIC_DIST_CONFIG_COMMON_NUM; i++) + writel_relaxed(context_gic_dist_common.config[i], + context_gic_dist_common.base + + GIC_DIST_CONFIG_SPI32 + i * 4); + + for (i = 0; i < GIC_DIST_SPI_TARGET_COMMON_NUM; i++) + writel_relaxed(context_gic_dist_common.spi_target[i], + context_gic_dist_common.base + + GIC_DIST_SPI_TARGET_SPI32 + i * 4); + + for (i = 0; i < GIC_DIST_PRI_COMMON_NUM; i++) + writel_relaxed(context_gic_dist_common.priority_level[i], + context_gic_dist_common.base + + GIC_DIST_PRI_SPI32 + i * 4); + + for (i = 0; i < GIC_DIST_ENABLE_SET_COMMON_NUM; i++) + writel_relaxed(context_gic_dist_common.enable_set[i], + context_gic_dist_common.base + + GIC_DIST_ENABLE_SET_SPI32 + i * 4); + + writel_relaxed(context_gic_dist_common.ns, + context_gic_dist_common.base + GIC_DIST_ENABLE_NS); +} + + + +/* + * Save GIC Dist CPU registers + * + * This needs to be called by all cpu:s which will not call + * save_gic_dist_common(). Only the registers of the GIC which are + * banked will be saved. + */ +static void save_gic_dist_cpu(struct context_gic_dist_cpu *c_gic) +{ + int i; + + for (i = 0; i < GIC_DIST_ENABLE_SET_CPU_NUM; i++) + c_gic->enable_set[i] = + readl_relaxed(c_gic->base + + GIC_DIST_ENABLE_SET_SPI0 + i * 4); + + for (i = 0; i < GIC_DIST_PRI_CPU_NUM; i++) + c_gic->priority_level[i] = + readl_relaxed(c_gic->base + + GIC_DIST_PRI_SPI0 + i * 4); + + for (i = 0; i < GIC_DIST_SPI_TARGET_CPU_NUM; i++) + c_gic->spi_target[i] = + readl_relaxed(c_gic->base + + GIC_DIST_SPI_TARGET_SPI0 + i * 4); + + for (i = 0; i < GIC_DIST_CONFIG_CPU_NUM; i++) + c_gic->config[i] = + readl_relaxed(c_gic->base + + GIC_DIST_CONFIG_SPI0 + i * 4); +} + +/* + * Restore GIC Dist CPU registers + * + * This needs to be called by all cpu:s which will not call + * restore_gic_dist_common(). Only the registers of the GIC which are + * banked will be saved. + */ +static void restore_gic_dist_cpu(struct context_gic_dist_cpu *c_gic) +{ + + int i; + + for (i = 0; i < GIC_DIST_CONFIG_CPU_NUM; i++) + writel_relaxed(c_gic->config[i], + c_gic->base + + GIC_DIST_CONFIG_SPI0 + i * 4); + + for (i = 0; i < GIC_DIST_SPI_TARGET_CPU_NUM; i++) + writel_relaxed(c_gic->spi_target[i], + c_gic->base + + GIC_DIST_SPI_TARGET_SPI0 + i * 4); + + for (i = 0; i < GIC_DIST_PRI_CPU_NUM; i++) + writel_relaxed(c_gic->priority_level[i], + c_gic->base + + GIC_DIST_PRI_SPI0 + i * 4); + + for (i = 0; i < GIC_DIST_ENABLE_SET_CPU_NUM; i++) + writel_relaxed(c_gic->enable_set[i], + c_gic->base + + GIC_DIST_ENABLE_SET_SPI0 + i * 4); +} + +/* + * Disable interrupts that are not necessary + * to have turned on during ApDeepSleep. + */ +void context_gic_dist_disable_unneeded_irqs(void) +{ + + writel(0xffffffff, + context_gic_dist_common.base + + GIC_DIST_ENABLE_CLEAR_0); + + writel(0xffffffff, + context_gic_dist_common.base + + GIC_DIST_ENABLE_CLEAR_32); + + /* Leave PRCMU IRQ 0 and 1 enabled */ + writel(0xffff3fff, + context_gic_dist_common.base + + GIC_DIST_ENABLE_CLEAR_64); + + writel(0xffffffff, + context_gic_dist_common.base + + GIC_DIST_ENABLE_CLEAR_96); + + writel(0xffffffff, + context_gic_dist_common.base + + GIC_DIST_ENABLE_CLEAR_128); + +} + +static void save_scu(void) +{ + context_scu.ctrl = + readl_relaxed(context_scu.base + SCU_CTRL); + context_scu.cpu_pwrstatus = + readl_relaxed(context_scu.base + SCU_CPU_STATUS); + context_scu.inv_all_nonsecure = + readl_relaxed(context_scu.base + SCU_INVALIDATE); + context_scu.filter_start_addr = + readl_relaxed(context_scu.base + SCU_FILTER_STARTADDR); + context_scu.filter_end_addr = + readl_relaxed(context_scu.base + SCU_FILTER_ENDADDR); + context_scu.access_ctrl_sac = + readl_relaxed(context_scu.base + SCU_ACCESS_CTRL_SAC); +} + +static void restore_scu(void) +{ + writel_relaxed(context_scu.ctrl, + context_scu.base + SCU_CTRL); + writel_relaxed(context_scu.cpu_pwrstatus, + context_scu.base + SCU_CPU_STATUS); + writel_relaxed(context_scu.inv_all_nonsecure, + context_scu.base + SCU_INVALIDATE); + writel_relaxed(context_scu.filter_start_addr, + context_scu.base + SCU_FILTER_STARTADDR); + writel_relaxed(context_scu.filter_end_addr, + context_scu.base + SCU_FILTER_ENDADDR); + writel_relaxed(context_scu.access_ctrl_sac, + context_scu.base + SCU_ACCESS_CTRL_SAC); +} + +/* + * Save VAPE context + */ +void context_vape_save(void) +{ + atomic_notifier_call_chain(&context_ape_notifier_list, + CONTEXT_APE_SAVE, NULL); + + if (cpu_is_u5500()) + u5500_context_save_icn(); + if (cpu_is_u8500()) + u8500_context_save_icn(); + + save_stm_ape(); + + save_tpiu(); + + save_prcc(); + +} + +/* + * Restore VAPE context + */ +void context_vape_restore(void) +{ + restore_prcc(); + + restore_tpiu(); + + restore_stm_ape(); + + if (cpu_is_u5500()) + u5500_context_restore_icn(); + if (cpu_is_u8500()) + u8500_context_restore_icn(); + + atomic_notifier_call_chain(&context_ape_notifier_list, + CONTEXT_APE_RESTORE, NULL); +} + +/* + * Save GPIO registers that might be modified + * for power save reasons. + */ +void context_gpio_save(void) +{ + int i; + + for (i = 0; i < GPIO_NUM_BANKS; i++) { + gpio_save[i][0] = readl(gpio_bankaddr[i] + NMK_GPIO_AFSLA); + gpio_save[i][1] = readl(gpio_bankaddr[i] + NMK_GPIO_AFSLB); + gpio_save[i][2] = readl(gpio_bankaddr[i] + NMK_GPIO_PDIS); + gpio_save[i][3] = readl(gpio_bankaddr[i] + NMK_GPIO_DIR); + gpio_save[i][4] = readl(gpio_bankaddr[i] + NMK_GPIO_DAT); + gpio_save[i][6] = readl(gpio_bankaddr[i] + NMK_GPIO_SLPC); + } +} + +/* + * Restore GPIO registers that might be modified + * for power save reasons. + */ +void context_gpio_restore(void) +{ + int i; + u32 output_state; + u32 pull_up; + u32 pull_down; + u32 pull; + + for (i = 0; i < GPIO_NUM_BANKS; i++) { + writel(gpio_save[i][2], gpio_bankaddr[i] + NMK_GPIO_PDIS); + + writel(gpio_save[i][3], gpio_bankaddr[i] + NMK_GPIO_DIR); + + /* Set the high outputs. outpute_state = GPIO_DIR & GPIO_DAT */ + output_state = gpio_save[i][3] & gpio_save[i][4]; + writel(output_state, gpio_bankaddr[i] + NMK_GPIO_DATS); + + /* + * Set the low outputs. + * outpute_state = ~(GPIO_DIR & GPIO_DAT) & GPIO_DIR + */ + output_state = ~(gpio_save[i][3] & gpio_save[i][4]) & + gpio_save[i][3]; + writel(output_state, gpio_bankaddr[i] + NMK_GPIO_DATC); + + /* + * Restore pull up/down. + * Only write pull up/down settings on inputs where + * PDIS is not set. + * pull = (~GPIO_DIR & ~GPIO_PDIS) + */ + pull = (~gpio_save[i][3] & ~gpio_save[i][2]); + nmk_gpio_read_pull(i, &pull_up); + + pull_down = pull & ~pull_up; + pull_up = pull & pull_up; + /* Set pull ups */ + writel(pull_up, gpio_bankaddr[i] + NMK_GPIO_DATS); + /* Set pull downs */ + writel(pull_down, gpio_bankaddr[i] + NMK_GPIO_DATC); + + writel(gpio_save[i][6], gpio_bankaddr[i] + NMK_GPIO_SLPC); + + } + +} + +/* + * Restore GPIO mux registers that might be modified + * for power save reasons. + */ +void context_gpio_restore_mux(void) +{ + int i; + + /* Change mux settings */ + for (i = 0; i < GPIO_NUM_BANKS; i++) { + writel(gpio_save[i][0], gpio_bankaddr[i] + NMK_GPIO_AFSLA); + writel(gpio_save[i][1], gpio_bankaddr[i] + NMK_GPIO_AFSLB); + } +} + +/* + * Safe sequence used to switch IOs between GPIO and Alternate-C mode: + * - Save SLPM registers (Not done.) + * - Set SLPM=0 for the IOs you want to switch. (We assume that all + * SLPM registers already are 0 except for the ones that wants to + * have the mux connected in sleep (e.g modem STM)). + * - Configure the GPIO registers for the IOs that are being switched + * - Set IOFORCE=1 + * - Modify the AFLSA/B registers for the IOs that are being switched + * - Set IOFORCE=0 + * - Restore SLPM registers (Not done.) + * - Any spurious wake up event during switch sequence to be ignored + * and cleared + */ +void context_gpio_mux_safe_switch(bool begin) +{ + int i; + + static u32 rwimsc[GPIO_NUM_BANKS]; + static u32 fwimsc[GPIO_NUM_BANKS]; + + if (begin) { + for (i = 0; i < GPIO_NUM_BANKS; i++) { + /* Save registers */ + rwimsc[i] = readl(gpio_bankaddr[i] + NMK_GPIO_RWIMSC); + fwimsc[i] = readl(gpio_bankaddr[i] + NMK_GPIO_FWIMSC); + + /* Prevent spurious wakeups */ + writel(0, gpio_bankaddr[i] + NMK_GPIO_RWIMSC); + writel(0, gpio_bankaddr[i] + NMK_GPIO_FWIMSC); + } + + ux500_pm_prcmu_set_ioforce(true); + } else { + ux500_pm_prcmu_set_ioforce(false); + + /* Restore wake up settings */ + for (i = 0; i < GPIO_NUM_BANKS; i++) { + writel(rwimsc[i], gpio_bankaddr[i] + NMK_GPIO_RWIMSC); + writel(fwimsc[i], gpio_bankaddr[i] + NMK_GPIO_FWIMSC); + } + + } + +} + +/* + * Save common + * + * This function must be called once for all cores before going to deep sleep. + */ +void context_varm_save_common(void) +{ + atomic_notifier_call_chain(&context_arm_notifier_list, + CONTEXT_ARM_COMMON_SAVE, NULL); + + /* Save common parts */ + save_gic_dist_common(); + save_scu(); +} + +/* + * Restore common + * + * This function must be called once for all cores when waking up from deep + * sleep. + */ +void context_varm_restore_common(void) +{ + /* Restore common parts */ + restore_scu(); + restore_gic_dist_common(); + + atomic_notifier_call_chain(&context_arm_notifier_list, + CONTEXT_ARM_COMMON_RESTORE, NULL); +} + +/* + * Save core + * + * This function must be called once for each cpu core before going to deep + * sleep. + */ +void context_varm_save_core(void) +{ + + int cpu = smp_processor_id(); + + atomic_notifier_call_chain(&context_arm_notifier_list, + CONTEXT_ARM_CORE_SAVE, NULL); + + per_cpu(varm_cp15_pointer, cpu) = per_cpu(varm_cp15_backup_stack, cpu); + + /* Save core */ + twd_save(); + save_gic_if_cpu(&per_cpu(context_gic_cpu, cpu)); + save_gic_dist_cpu(&per_cpu(context_gic_dist_cpu, cpu)); + context_save_cp15_registers(&per_cpu(varm_cp15_pointer, cpu)); + +} + +/* + * Restore core + * + * This function must be called once for each cpu core when waking up from + * deep sleep. + */ +void context_varm_restore_core(void) +{ + int cpu = smp_processor_id(); + + /* Restore core */ + context_restore_cp15_registers(&per_cpu(varm_cp15_pointer, cpu)); + restore_gic_dist_cpu(&per_cpu(context_gic_dist_cpu, cpu)); + restore_gic_if_cpu(&per_cpu(context_gic_cpu, cpu)); + twd_restore(); + + atomic_notifier_call_chain(&context_arm_notifier_list, + CONTEXT_ARM_CORE_RESTORE, NULL); + +} + +/* + * Save CPU registers + * + * This function saves ARM registers. + */ +void context_save_cpu_registers(void) +{ + int cpu = smp_processor_id(); + + per_cpu(varm_registers_pointer, cpu) = + per_cpu(varm_registers_backup_stack, cpu); + context_save_arm_registers(&per_cpu(varm_registers_pointer, cpu)); +} + +/* + * Restore CPU registers + * + * This function restores ARM registers. + */ +void context_restore_cpu_registers(void) +{ + int cpu = smp_processor_id(); + + context_restore_arm_registers(&per_cpu(varm_registers_pointer, cpu)); +} + +/* + * This function stores CP15 registers related to cache and mmu + * in backup SRAM. It also stores stack pointer, CPSR + * and return address for the PC in backup SRAM and + * does wait for interrupt. + */ +void context_save_to_sram_and_wfi(bool cleanL2cache) +{ + int cpu = smp_processor_id(); + + context_save_to_sram_and_wfi_internal(backup_sram_storage[cpu], + cleanL2cache); +} + +static int __init context_init(void) +{ + int i; + void __iomem *ux500_backup_ptr; + + /* allocate backup pointer for RAM data */ + ux500_backup_ptr = (void *)__get_free_pages(GFP_KERNEL, + get_order(U8500_BACKUPRAM_SIZE)); + + if (!ux500_backup_ptr) { + pr_warning("context: could not allocate backup memory\n"); + return -ENOMEM; + } + + /* + * ROM code addresses to store backup contents, + * pass the physical address of back up to ROM code + */ + writel(virt_to_phys(ux500_backup_ptr), + IO_ADDRESS(U8500_EXT_RAM_LOC_BACKUPRAM_ADDR)); + + + if (cpu_is_u5500()) { + writel(IO_ADDRESS(U5500_PUBLIC_BOOT_ROM_BASE), + IO_ADDRESS(U8500_CPU0_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR)); + + writel(IO_ADDRESS(U5500_PUBLIC_BOOT_ROM_BASE), + IO_ADDRESS(U8500_CPU1_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR)); + + context_tpiu.base = ioremap(U5500_TPIU_BASE, SZ_4K); + context_stm_ape.base = ioremap(U5500_STM_REG_BASE, SZ_4K); + context_scu.base = ioremap(U5500_SCU_BASE, SZ_4K); + + context_prcc[0].base = ioremap(U5500_CLKRST1_BASE, SZ_4K); + context_prcc[1].base = ioremap(U5500_CLKRST2_BASE, SZ_4K); + context_prcc[2].base = ioremap(U5500_CLKRST3_BASE, SZ_4K); + context_prcc[3].base = ioremap(U5500_CLKRST5_BASE, SZ_4K); + context_prcc[4].base = ioremap(U5500_CLKRST6_BASE, SZ_4K); + + context_gic_dist_common.base = ioremap(U5500_GIC_DIST_BASE, SZ_4K); + per_cpu(context_gic_cpu, 0).base = ioremap(U5500_GIC_CPU_BASE, SZ_4K); + } else if (cpu_is_u8500()) { + /* Give logical address to backup RAM. For both CPUs */ + writel(IO_ADDRESS(U8500_PUBLIC_BOOT_ROM_BASE), + IO_ADDRESS(U8500_CPU0_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR)); + + writel(IO_ADDRESS(U8500_PUBLIC_BOOT_ROM_BASE), + IO_ADDRESS(U8500_CPU1_BACKUPRAM_ADDR_PUBLIC_BOOT_ROM_LOG_ADDR)); + + context_tpiu.base = ioremap(U8500_TPIU_BASE, SZ_4K); + context_stm_ape.base = ioremap(U8500_STM_REG_BASE, SZ_4K); + context_scu.base = ioremap(U8500_SCU_BASE, SZ_4K); + + /* PERIPH4 is always on, so no need saving prcc */ + context_prcc[0].base = ioremap(U8500_CLKRST1_BASE, SZ_4K); + context_prcc[1].base = ioremap(U8500_CLKRST2_BASE, SZ_4K); + context_prcc[2].base = ioremap(U8500_CLKRST3_BASE, SZ_4K); + context_prcc[3].base = ioremap(U8500_CLKRST5_BASE, SZ_4K); + context_prcc[4].base = ioremap(U8500_CLKRST6_BASE, SZ_4K); + + context_gic_dist_common.base = ioremap(U8500_GIC_DIST_BASE, SZ_4K); + per_cpu(context_gic_cpu, 0).base = ioremap(U8500_GIC_CPU_BASE, SZ_4K); + } + + per_cpu(context_gic_dist_cpu, 0).base = context_gic_dist_common.base; + + for (i = 1; i < num_possible_cpus(); i++) { + per_cpu(context_gic_cpu, i).base + = per_cpu(context_gic_cpu, 0).base; + per_cpu(context_gic_dist_cpu, i).base + = per_cpu(context_gic_dist_cpu, 0).base; + } + + for (i = 0; i < ARRAY_SIZE(context_prcc); i++) { + const int clusters[] = {1, 2, 3, 5, 6}; + char clkname[10]; + + snprintf(clkname, sizeof(clkname), "PERIPH%d", clusters[i]); + + context_prcc[i].clk = clk_get_sys(clkname, NULL); + BUG_ON(IS_ERR(context_prcc[i].clk)); + } + + if (cpu_is_u8500()) { + u8500_context_init(); + } else if (cpu_is_u5500()) { + u5500_context_init(); + } else { + printk(KERN_ERR "context: unknown hardware!\n"); + return -EINVAL; + } + + return 0; +} +subsys_initcall(context_init); diff --git a/arch/arm/mach-ux500/pm/context_arm.S b/arch/arm/mach-ux500/pm/context_arm.S new file mode 100644 index 00000000000..55e2accc85f --- /dev/null +++ b/arch/arm/mach-ux500/pm/context_arm.S @@ -0,0 +1,385 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com> + * Rickard Andersson <rickard.andersson@stericsson.com> for + * ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#include <linux/linkage.h> +#include <mach/hardware.h> +#include <asm/hardware/cache-l2x0.h> + +/* + * Save and increment macro + */ +.macro SAVE_AND_INCREMENT FROM_REG TO_REG + str \FROM_REG, [\TO_REG], #+4 +.endm + +/* + * Decrement and restore macro + */ +.macro DECREMENT_AND_RESTORE FROM_REG TO_REG + ldr \TO_REG, [\FROM_REG, #-4]! +.endm + +/* + * Save ARM registers + * + * This function must be called in supervisor mode. + * + * r0 = address to backup stack pointer + * + * Backup stack operations: + * + {sp, lr}^ + * + cpsr + * + {r3, r8-r14} (FIQ mode: r3=spsr) + * + {r3, r13, r14} (IRQ mode: r3=spsr) + * + {r3, r13, r14} (abort mode: r3=spsr) + * + {r3, r13, r14} (undef mode: r3=spsr) + */ + .align + .section ".text", "ax" +ENTRY(context_save_arm_registers) + stmfd sp!, {r1, r2, r3, lr} @ Save on stack + ldr r1, [r0] @ Read backup stack pointer + + stmia r1, {sp, lr}^ @ Store user mode sp and lr + @ registers + add r1, r1, #8 @ Update backup pointer (not + @ done in previous instruction) + + mrs r2, cpsr @ Get CPSR + SAVE_AND_INCREMENT r2 r1 @ Save CPSR register + orr r2, r2, #0xc0 @ Disable FIQ and IRQ + bic r2, r2, #0x1f @ Setup r2 to change mode + + @ The suffix to CPSR refers to which field(s) of the CPSR is + @ rereferenced (you can specify one or more). Defined fields are: + @ + @ c - control + @ x - extension + @ s - status + @ f - flags + + orr r3, r2, #0x11 @ Save FIQ mode registers + msr cpsr_cxsf, r3 + mrs r3, spsr + stmia r1!, {r3, r8-r14} + + orr r3, r2, #0x12 @ Save IRQ mode registers + msr cpsr_cxsf, r3 + mrs r3, spsr + stmia r1!, {r3, r13, r14} + + orr r3, r2, #0x17 @ Save abort mode registers + + @ common mode registers + msr cpsr_cxsf, r3 + mrs r3, spsr + stmia r1!, {r3, r13, r14} + + orr r3, r2, #0x1B @ Save undef mode registers + msr cpsr_cxsf, r3 + mrs r3, spsr + stmia r1!, {r3, r13, r14} + + orr r3, r2, #0x13 @ Return to supervisor mode + msr cpsr_cxsf, r3 + + str r1, [r0] @ Write backup stack pointer + ldmfd sp!, {r1, r2, r3, pc} @ Restore registers and return + + + +/* + * Restore ARM registers + * + * This function must be called in supervisor mode. + * + * r0 = address to backup stack pointer + * + * Backup stack operations: + * - {r3, r13, r14} (undef mode: spsr=r3) + * - {r3, r13, r14} (abort mode: spsr=r3) + * - {r3, r13, r14} (IRQ mode: spsr=r3) + * - {r3, r8-r14} (FIQ mode: spsr=r3) + * - cpsr + * - {sp, lr}^ + */ + .align + .section ".text", "ax" +ENTRY(context_restore_arm_registers) + stmfd sp!, {r1, r2, r3, lr} @ Save on stack + ldr r1, [r0] @ Read backup stack pointer + + mrs r2, cpsr @ Get CPSR + orr r2, r2, #0xc0 @ Disable FIQ and IRQ + bic r2, r2, #0x1f @ Setup r2 to change mode + + orr r3, r2, #0x1b @ Restore undef mode registers + msr cpsr_cxsf, r3 + ldmdb r1!, {r3, r13, r14} + msr spsr_cxsf, r3 + + orr r3, r2, #0x17 @ Restore abort mode registers + msr cpsr_cxsf, r3 + ldmdb r1!, {r3, r13, r14} + msr spsr_cxsf, r3 + + orr r3, r2, #0x12 @ Restore IRQ mode registers + msr cpsr_cxsf, r3 + ldmdb r1!, {r3, r13, r14} + msr spsr_cxsf, r3 + + orr r3, r2, #0x11 @ Restore FIQ mode registers + msr cpsr_cxsf, r3 + ldmdb r1!, {r3, r8-r14} + msr spsr_cxsf, r3 + + DECREMENT_AND_RESTORE r1 r3 @ Restore cpsr register + msr cpsr_cxsf, r3 + + ldmdb r1, {sp, lr}^ @ Restore sp and lr registers + sub r1, r1, #8 @ Update backup pointer (not + @ done in previous instruction) + + str r1, [r0] @ Write backup stack pointer + ldmfd sp!, {r1, r2, r3, pc} @ Restore registers and return + + + +/* + * Save CP15 registers + * + * This function must be called in supervisor mode. + * + * r0 = address to backup stack pointer + * + * TTBR0, TTBR1, TTBRC, DACR CP15 registers are restored by boot ROM from SRAM. + */ + .align 4 + .section ".text", "ax" +ENTRY(context_save_cp15_registers) + stmfd sp!, {r1, r2, r3, lr} @ Save on stack (r3 is saved due + @ to 8 byte aligned stack) + ldr r1, [r0] @ Read backup stack pointer + + mrc p15, 0, r2, c12, c0, 0 @ Read Non-secure Vector Base + @ Address Register + SAVE_AND_INCREMENT r2 r1 + + mrc p15, 0, r2, c10, c2, 0 @ Access primary memory region + @ remap register + SAVE_AND_INCREMENT r2 r1 + + mrc p15, 0, r2, c10, c2, 1 @ Access normal memory region + @ remap register + SAVE_AND_INCREMENT r2 r1 + + mrc p15, 0, r2, c13, c0, 1 @ Read Context ID Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c13, c0, 2 @ Read Thread ID registers, + @ this register is both user + @ and privileged R/W accessible + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c13, c0, 3 @ Read Thread ID registers, + @ this register is user + @ read-only and privileged R/W + @ accessible. + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c13, c0, 4 @ Read Thread ID registers, + @ this register is privileged + @ R/W accessible only. + SAVE_AND_INCREMENT r2 r1 + + mrc p15, 2, r2, c0, c0, 0 @ Cache Size Selection Register + SAVE_AND_INCREMENT r2 r1 + + mrc p15, 0, r2, c9, c12, 0 @ Read PMNC Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c12, 1 @ Read PMCNTENSET Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c12, 5 @ Read PMSELR Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c13, 0 @ Read PMCCNTR Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c13, 1 @ Read PMXEVTYPER Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c14, 0 @ Read PMUSERENR Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c14, 1 @ Read PMINTENSET Register + SAVE_AND_INCREMENT r2 r1 + mrc p15, 0, r2, c9, c14, 2 @ Read PMINTENCLR Register + SAVE_AND_INCREMENT r2 r1 + + mrc p15, 0, r2, c1, c0, 2 @ Read CPACR Register + SAVE_AND_INCREMENT r2 r1 + + str r1, [r0] @ Write backup stack pointer + ldmfd sp!, {r1, r2, r3, pc} @ Restore registers and return + + + +/* + * Restore CP15 registers + * + * This function must be called in supervisor mode. + * + * r0 = address to backup stack pointer + */ + .align 4 + .section ".text", "ax" +ENTRY(context_restore_cp15_registers) + stmfd sp!, {r1, r2, r3, lr} @ Save on stack (r3 is saved due + @ to 8 byte aligned stack) + ldr r1, [r0] @ Read backup stack pointer + + DECREMENT_AND_RESTORE r1 r2 @ Write CPACR register + mcr p15, 0, r2, c1, c0, 2 + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c14, 2 @ Write PMINTENCLR Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c14, 1 @ Write PMINTENSET Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c14, 0 @ Write PMUSERENR Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c13, 1 @ Write PMXEVTYPER Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c13, 0 @ Write PMCCNTR Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c12, 5 @ Write PMSELR Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c12, 1 @ Write PMCNTENSET Register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c9, c12, 0 @ Write PMNC Register + + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 2, r2, c0, c0, 0 @ Cache Size Selection Register + + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c13, c0, 4 @ Write Thread ID registers, + @ this register is privileged + @ R/W accessible only + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c13, c0, 3 @ Write Thread ID registers, + @ this register is user + @ read-only and privileged R/W + @ accessible + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c13, c0, 2 @ Write Thread ID registers, + @ this register is both user + @ and privileged R/W accessible + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c13, c0, 1 @ Write Context ID Register + + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c10, c2, 1 @ Access normal memory region + @ remap register + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c10, c2, 0 @ Access primary memory region + @ remap register + + DECREMENT_AND_RESTORE r1 r2 + mcr p15, 0, r2, c12, c0, 0 @ Write Non-secure Vector Base + @ Address Register + + str r1, [r0] @ Write backup stack pointer + ldmfd sp!, {r1, r2, r3, pc} @ Restore registers and return + + +/* + * L1 cache clean function. Commit 'dirty' data from L1 + * to L2 cache. + * + * r0, r1, r2, used locally + * + */ + .align 4 + .section ".text", "ax" +ENTRY(context_clean_l1_cache_all) + + mov r0, #0 @ swith to cache level 0 + @ (L1 cache) + mcr p15, 2, r0, c0, c0, 0 @ select current cache level + @ in cssr + + dmb + mov r1, #0 @ r1 = way index +wayLoopL1clean: + mov r0, #0 @ r0 = line index +lineLoopL1clean: + mov r2, r1, lsl #30 @ TODO: OK to hard-code + @ SoC-specific L1 cache details? + add r2, r0, lsl #5 + mcr p15, 0, r2, c7, c10, 2 @ Clean cache by set/way + add r0, r0, #1 + cmp r0, #256 @ TODO: Ok with hard-coded + @ set/way sizes or do we have to + @ read them from ARM regs? Is it + @ set correctly in silicon? + bne lineLoopL1clean + add r1, r1, #1 + cmp r1, #4 @ TODO: Ditto, sizes... + bne wayLoopL1clean + + dsb + isb + mov pc, lr + +ENDPROC(context_clean_l1_cache_all) + +/* + * Last saves to backup RAM, cache clean and WFI + * + * r0 = address to backup_sram_storage base adress + * r1 = indicate whether also L2 cache should be cleaned + */ + .align 4 + .section ".text", "ax" +ENTRY(context_save_to_sram_and_wfi_internal) + + stmfd sp!, {r2-r12, lr} @ save on stack. + + mrc p15, 0, r2, c1, c0, 0 @ read cp15 system control + @ register + str r2, [r0, #0x00] + mrc p15, 0, r2, c2, c0, 0 @ read cp15 ttb0 register + str r2, [r0, #0x04] + mrc p15, 0, r2, c2, c0, 1 @ read cp15 ttb1 register + str r2, [r0, #0x08] + mrc p15, 0, r2, c2, c0, 2 @ read cp15 ttb control register + str r2, [r0, #0x0C] + mrc p15, 0, r2, c3, c0, 0 @ read domain access control + @ register + str r2, [r0, #0x10] + + ldr r2, =return_here + str r2, [r0, #0x14] @ save program counter restore + @ value to backup_sram_storage + mrs r2, cpsr + str r2, [r0, #0x18] @ save cpsr to + @ backup_sram_storage + str sp, [r0, #0x1c] @ save sp to backup_sram_storage + + mov r4, r1 @ Set r4 = cleanL2cache, r1 + @ will be destroyed by + @ v7_clean_l1_cache_all + + bl context_clean_l1_cache_all @ Commit all dirty data in L1 + @ cache to L2 without + @ invalidating + + dsb @ data synchronization barrier + isb @ instruction synchronization + @ barrier + wfi @ wait for interrupt + +return_here: @ both cores return here + @ now we are out deep sleep + @ with all the context lost + @ except pc, sp and cpsr + + ldmfd sp!, {r2-r12, pc} @ restore from stack + diff --git a/arch/arm/mach-ux500/pm/performance.c b/arch/arm/mach-ux500/pm/performance.c new file mode 100644 index 00000000000..ad35f5ad07c --- /dev/null +++ b/arch/arm/mach-ux500/pm/performance.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Johan Rudholm <johan.rudholm@stericsson.com> + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + */ + +#include <linux/kernel.h> +#include <linux/genhd.h> +#include <linux/major.h> +#include <linux/cdev.h> +#include <linux/kernel_stat.h> +#include <linux/workqueue.h> +#include <linux/kernel.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/cpu.h> +#include <linux/pm_qos_params.h> + +#include <mach/irqs.h> + +#define WLAN_PROBE_DELAY 3000 /* 3 seconds */ +#define WLAN_LIMIT (3000/3) /* If we have more than 1000 irqs per second */ + +/* + * MMC TODO: + * o Develop a more power-aware algorithm + * o Make the parameters visible through debugfs + * o Get the value of CONFIG_MMC_BLOCK_MINORS in runtime instead, since + * it may be altered by drivers/mmc/card/block.c + */ + +/* Sample reads and writes every n ms */ +#define PERF_MMC_PROBE_DELAY 1000 +/* Read threshold, sectors/second */ +#define PERF_MMC_LIMIT_READ 10240 +/* Write threshold, sectors/second */ +#define PERF_MMC_LIMIT_WRITE 8192 +/* Nr of MMC devices */ +#define PERF_MMC_HOSTS 8 + +/* + * Rescan for new MMC devices every + * PERF_MMC_PROBE_DELAY * PERF_MMC_RESCAN_CYCLES ms + */ +#define PERF_MMC_RESCAN_CYCLES 10 + +#ifdef CONFIG_MMC_BLOCK +static struct delayed_work work_mmc; +#endif + +static struct delayed_work work_wlan_workaround; +static struct pm_qos_request_list wlan_pm_qos_latency; +static bool wlan_pm_qos_is_latency_0; + +static void wlan_load(struct work_struct *work) +{ + int cpu; + unsigned int num_irqs = 0; + static unsigned int old_num_irqs = UINT_MAX; + + for_each_online_cpu(cpu) + num_irqs += kstat_irqs_cpu(IRQ_DB8500_SDMMC1, cpu); + + if ((num_irqs > old_num_irqs) && + (num_irqs - old_num_irqs) > WLAN_LIMIT) { + prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, + "wlan", 125); + if (!wlan_pm_qos_is_latency_0) { + /* + * The wake up latency is set to 0 to prevent + * the system from going to sleep. This improves + * the wlan throughput in DMA mode. + * The wake up latency from sleep adds ~5% overhead + * for TX in some cases. + * This change doesn't increase performance for wlan + * PIO since the CPU usage prevents sleep in this mode. + */ + pm_qos_add_request(&wlan_pm_qos_latency, + PM_QOS_CPU_DMA_LATENCY, 0); + wlan_pm_qos_is_latency_0 = true; + } + } else { + prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, + "wlan", 25); + if (wlan_pm_qos_is_latency_0) { + pm_qos_remove_request(&wlan_pm_qos_latency); + wlan_pm_qos_is_latency_0 = false; + } + } + + old_num_irqs = num_irqs; + + schedule_delayed_work_on(0, + &work_wlan_workaround, + msecs_to_jiffies(WLAN_PROBE_DELAY)); +} + +#ifdef CONFIG_MMC_BLOCK +/* + * Loop through every CONFIG_MMC_BLOCK_MINORS'th minor device for + * MMC_BLOCK_MAJOR, get the struct gendisk for each device. Returns + * nr of found disks. Populate mmc_disks. + */ +static int scan_mmc_devices(struct gendisk *mmc_disks[]) +{ + dev_t devnr; + int i, j = 0, part; + struct gendisk *mmc_devices[256 / CONFIG_MMC_BLOCK_MINORS]; + + memset(&mmc_devices, 0, sizeof(mmc_devices)); + + for (i = 0; i * CONFIG_MMC_BLOCK_MINORS < 256; i++) { + devnr = MKDEV(MMC_BLOCK_MAJOR, i * CONFIG_MMC_BLOCK_MINORS); + mmc_devices[i] = get_gendisk(devnr, &part); + + /* Invalid capacity of device, do not add to list */ + if (!mmc_devices[i] || !get_capacity(mmc_devices[i])) + continue; + + mmc_disks[j] = mmc_devices[i]; + j++; + + if (j == PERF_MMC_HOSTS) + break; + } + + return j; +} + +/* + * Sample sectors read and written to any MMC devices, update PRCMU + * qos requirement + */ +static void mmc_load(struct work_struct *work) +{ + static unsigned long long old_sectors_read[PERF_MMC_HOSTS]; + static unsigned long long old_sectors_written[PERF_MMC_HOSTS]; + static struct gendisk *mmc_disks[PERF_MMC_HOSTS]; + static int cycle, nrdisk; + static bool old_mode; + unsigned long long sectors; + bool new_mode = false; + int i; + + if (!cycle) { + memset(&mmc_disks, 0, sizeof(mmc_disks)); + nrdisk = scan_mmc_devices(mmc_disks); + cycle = PERF_MMC_RESCAN_CYCLES; + } + cycle--; + + for (i = 0; i < nrdisk; i++) { + sectors = part_stat_read(&(mmc_disks[i]->part0), + sectors[READ]); + + if (old_sectors_read[i] && + sectors > old_sectors_read[i] && + (sectors - old_sectors_read[i]) > + PERF_MMC_LIMIT_READ) + new_mode = true; + + old_sectors_read[i] = sectors; + sectors = part_stat_read(&(mmc_disks[i]->part0), + sectors[WRITE]); + + if (old_sectors_written[i] && + sectors > old_sectors_written[i] && + (sectors - old_sectors_written[i]) > + PERF_MMC_LIMIT_WRITE) + new_mode = true; + + old_sectors_written[i] = sectors; + } + + if (!old_mode && new_mode) + prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, + "mmc", 125); + + if (old_mode && !new_mode) + prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, + "mmc", 25); + + old_mode = new_mode; + + schedule_delayed_work(&work_mmc, + msecs_to_jiffies(PERF_MMC_PROBE_DELAY)); + +} +#endif /* CONFIG_MMC_BLOCK */ + +static int __init performance_register(void) +{ + int ret; + +#ifdef CONFIG_MMC_BLOCK + prcmu_qos_add_requirement(PRCMU_QOS_ARM_OPP, "mmc", 25); + + INIT_DELAYED_WORK_DEFERRABLE(&work_mmc, mmc_load); + + ret = schedule_delayed_work(&work_mmc, + msecs_to_jiffies(PERF_MMC_PROBE_DELAY)); + if (ret) { + pr_err("ux500: performance: Fail to schedudle mmc work\n"); + goto out; + } +#endif + + prcmu_qos_add_requirement(PRCMU_QOS_ARM_OPP, "wlan", 25); + + INIT_DELAYED_WORK_DEFERRABLE(&work_wlan_workaround, + wlan_load); + + ret = schedule_delayed_work_on(0, + &work_wlan_workaround, + msecs_to_jiffies(WLAN_PROBE_DELAY)); + if (ret) { + pr_err("ux500: performance: Fail to schedudle wlan work\n"); + goto out; + } +out: + return ret; +} +late_initcall(performance_register); diff --git a/arch/arm/mach-ux500/pm/pm.c b/arch/arm/mach-ux500/pm/pm.c new file mode 100644 index 00000000000..d986d1024d0 --- /dev/null +++ b/arch/arm/mach-ux500/pm/pm.c @@ -0,0 +1,231 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Rickard Andersson <rickard.andersson@stericsson.com> for + * ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#include <linux/io.h> +#include <linux/percpu.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/gpio/nomadik.h> + +#include <asm/hardware/gic.h> +#include <asm/processor.h> + +#include <mach/hardware.h> +#include <mach/pm.h> + +#define STABILIZATION_TIME 30 /* us */ +#define GIC_FREEZE_DELAY 1 /* us */ + +#define PRCM_ARM_WFI_STANDBY_CPU0_WFI 0x8 +#define PRCM_ARM_WFI_STANDBY_CPU1_WFI 0x10 + +/* Dual A9 core interrupt management unit registers */ +#define PRCM_A9_MASK_REQ (_PRCMU_BASE + 0x328) +#define PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ 0x1 +#define PRCM_A9_MASK_ACK (_PRCMU_BASE + 0x32c) + +#define PRCM_ARMITMSK31TO0 (_PRCMU_BASE + 0x11c) +#define PRCM_ARMITMSK63TO32 (_PRCMU_BASE + 0x120) +#define PRCM_ARMITMSK95TO64 (_PRCMU_BASE + 0x124) +#define PRCM_ARMITMSK127TO96 (_PRCMU_BASE + 0x128) +#define PRCM_POWER_STATE_VAL (_PRCMU_BASE + 0x25C) +#define PRCM_ARMITVAL31TO0 (_PRCMU_BASE + 0x260) +#define PRCM_ARMITVAL63TO32 (_PRCMU_BASE + 0x264) +#define PRCM_ARMITVAL95TO64 (_PRCMU_BASE + 0x268) +#define PRCM_ARMITVAL127TO96 (_PRCMU_BASE + 0x26C) + +/* ARM WFI Standby signal register */ +#define PRCM_ARM_WFI_STANDBY (_PRCMU_BASE + 0x130) + +/* IO force */ +#define PRCM_IOCR (_PRCMU_BASE + 0x310) +#define PRCM_IOCR_IOFORCE 0x1 + +static u32 u8500_gpio_banks[] = {U8500_GPIOBANK0_BASE, + U8500_GPIOBANK1_BASE, + U8500_GPIOBANK2_BASE, + U8500_GPIOBANK3_BASE, + U8500_GPIOBANK4_BASE, + U8500_GPIOBANK5_BASE, + U8500_GPIOBANK6_BASE, + U8500_GPIOBANK7_BASE, + U8500_GPIOBANK8_BASE}; + +static u32 u5500_gpio_banks[] = {U5500_GPIOBANK0_BASE, + U5500_GPIOBANK1_BASE, + U5500_GPIOBANK2_BASE, + U5500_GPIOBANK3_BASE, + U5500_GPIOBANK4_BASE, + U5500_GPIOBANK5_BASE, + U5500_GPIOBANK6_BASE, + U5500_GPIOBANK7_BASE}; + +static u32 ux500_gpio_wks[ARRAY_SIZE(u8500_gpio_banks)]; + +inline int ux500_pm_arm_on_ext_clk(bool leave_arm_pll_on) +{ + return 0; +} + +/* Decouple GIC from the interrupt bus */ +void ux500_pm_gic_decouple(void) +{ + writel(readl(PRCM_A9_MASK_REQ) | PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ, + PRCM_A9_MASK_REQ); + + while (!readl(PRCM_A9_MASK_REQ)) + cpu_relax(); + + /* TODO: Use the ack bit when possible */ + udelay(GIC_FREEZE_DELAY); /* Wait for the GIC to freeze */ +} + +/* Recouple GIC with the interrupt bus */ +void ux500_pm_gic_recouple(void) +{ + writel((readl(PRCM_A9_MASK_REQ) & ~PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ), + PRCM_A9_MASK_REQ); + + /* TODO: Use the ack bit when possible */ +} + +#define GIC_NUMBER_REGS 5 +bool ux500_pm_gic_pending_interrupt(void) +{ + u32 pr; /* Pending register */ + u32 er; /* Enable register */ + int i; + + /* 5 registers. STI & PPI not skipped */ + for (i = 0; i < GIC_NUMBER_REGS; i++) { + + pr = readl_relaxed(__io_address(U8500_GIC_DIST_BASE) + + GIC_DIST_PENDING_SET + i * 4); + er = readl_relaxed(__io_address(U8500_GIC_DIST_BASE) + + GIC_DIST_ENABLE_SET + i * 4); + + if (pr & er) + return true; /* There is a pending interrupt */ + } + return false; +} + +#define GIC_NUMBER_SPI_REGS 4 +bool ux500_pm_prcmu_pending_interrupt(void) +{ + u32 it; + u32 im; + int i; + + for (i = 0; i < GIC_NUMBER_SPI_REGS; i++) { /* There are 4 registers */ + + it = readl(PRCM_ARMITVAL31TO0 + i * 4); + im = readl(PRCM_ARMITMSK31TO0 + i * 4); + + if (it & im) + return true; /* There is a pending interrupt */ + } + + return false; +} + +void ux500_pm_prcmu_set_ioforce(bool enable) +{ + if (enable) + writel(readl(PRCM_IOCR) | PRCM_IOCR_IOFORCE, PRCM_IOCR); + else + writel(readl(PRCM_IOCR) & ~PRCM_IOCR_IOFORCE, PRCM_IOCR); +} + +void ux500_pm_prcmu_copy_gic_settings(void) +{ + u32 er; /* Enable register */ + int i; + + for (i = 0; i < GIC_NUMBER_SPI_REGS; i++) { /* 4*32 SPI interrupts */ + /* +1 due to skip STI and PPI */ + er = readl_relaxed(__io_address(U8500_GIC_DIST_BASE) + + GIC_DIST_ENABLE_SET + (i + 1) * 4); + writel(er, PRCM_ARMITMSK31TO0 + i * 4); + } +} + +void ux500_pm_gpio_save_wake_up_status(void) +{ + int num_banks; + u32 *banks; + int i; + + if (cpu_is_u5500()) { + num_banks = ARRAY_SIZE(u5500_gpio_banks); + banks = u5500_gpio_banks; + } else { + num_banks = ARRAY_SIZE(u8500_gpio_banks); + banks = u8500_gpio_banks; + } + + nmk_gpio_clocks_enable(); + + for (i = 0; i < num_banks; i++) + ux500_gpio_wks[i] = readl(__io_address(banks[i]) + NMK_GPIO_WKS); + + nmk_gpio_clocks_disable(); +} + +u32 ux500_pm_gpio_read_wake_up_status(unsigned int bank_num) +{ + if (WARN_ON(cpu_is_u5500() && bank_num >= + ARRAY_SIZE(u5500_gpio_banks))) + return 0; + + if (WARN_ON(cpu_is_u8500() && bank_num >= + ARRAY_SIZE(u8500_gpio_banks))) + return 0; + + return ux500_gpio_wks[bank_num]; +} + +/* Check if the other CPU is in WFI */ +bool ux500_pm_other_cpu_wfi(void) +{ + if (smp_processor_id()) { + /* We are CPU 1 => check if CPU0 is in WFI */ + if (readl(PRCM_ARM_WFI_STANDBY) & + PRCM_ARM_WFI_STANDBY_CPU0_WFI) + return true; + } else { + /* We are CPU 0 => check if CPU1 is in WFI */ + if (readl(PRCM_ARM_WFI_STANDBY) & + PRCM_ARM_WFI_STANDBY_CPU1_WFI) + return true; + } + + return false; +} + +/* PRCM_ACK_MB0_AP_PWRSTTR_STATUS */ +#define DB8500_PRCMU_STATUS_REGISTER 0x801b8e08 +#define DB5500_PRCMU_STATUS_REGISTER 0x80168f38 + +enum prcmu_idle_stat ux500_pm_prcmu_idle_stat(void) +{ + u32 val; + void __iomem *prcmu_status_reg; + + if (cpu_is_u8500()) + prcmu_status_reg = __io_address(DB8500_PRCMU_STATUS_REGISTER); + else if (cpu_is_u5500()) + prcmu_status_reg = __io_address(DB5500_PRCMU_STATUS_REGISTER); + else + ux500_unknown_soc(); + + val = readl(prcmu_status_reg) & 0xff; + + return (enum prcmu_idle_stat)val; +} diff --git a/arch/arm/mach-ux500/pm/prcmu-qos-power.c b/arch/arm/mach-ux500/pm/prcmu-qos-power.c new file mode 100644 index 00000000000..e095eed4b70 --- /dev/null +++ b/arch/arm/mach-ux500/pm/prcmu-qos-power.c @@ -0,0 +1,702 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Author: Martin Persson + * Per Fransson <per.xx.fransson@stericsson.com> + * + * Quality of Service for the U8500 PRCM Unit interface driver + * + * Strongly influenced by kernel/pm_qos_params.c. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/uaccess.h> +#include <linux/cpufreq.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/cpufreq-dbx500.h> + +#include <mach/prcmu-debug.h> + +#define ARM_THRESHOLD_FREQ (400000) + +static int qos_delayed_cpufreq_notifier(struct notifier_block *, + unsigned long, void *); + +static s32 cpufreq_requirement_queued; +static s32 cpufreq_requirement_set; + +/* + * locking rule: all changes to requirements or prcmu_qos_object list + * and prcmu_qos_objects need to happen with prcmu_qos_lock + * held, taken with _irqsave. One lock to rule them all + */ +struct requirement_list { + struct list_head list; + union { + s32 value; + s32 usec; + s32 kbps; + }; + char *name; +}; + +static s32 max_compare(s32 v1, s32 v2); + +struct prcmu_qos_object { + struct requirement_list requirements; + struct blocking_notifier_head *notifiers; + struct miscdevice prcmu_qos_power_miscdev; + char *name; + s32 default_value; + s32 force_value; + atomic_t target_value; + s32 (*comparitor)(s32, s32); +}; + +static struct prcmu_qos_object null_qos; +static BLOCKING_NOTIFIER_HEAD(prcmu_ape_opp_notifier); +static BLOCKING_NOTIFIER_HEAD(prcmu_ddr_opp_notifier); + +static struct prcmu_qos_object ape_opp_qos = { + .requirements = { + LIST_HEAD_INIT(ape_opp_qos.requirements.list) + }, + .notifiers = &prcmu_ape_opp_notifier, + .name = "ape_opp", + /* Target value in % APE OPP */ + .default_value = 50, + .force_value = 0, + .target_value = ATOMIC_INIT(0), + .comparitor = max_compare +}; + +static struct prcmu_qos_object ddr_opp_qos = { + .requirements = { + LIST_HEAD_INIT(ddr_opp_qos.requirements.list) + }, + .notifiers = &prcmu_ddr_opp_notifier, + .name = "ddr_opp", + /* Target value in % DDR OPP */ + .default_value = 25, + .force_value = 0, + .target_value = ATOMIC_INIT(0), + .comparitor = max_compare +}; + +static struct prcmu_qos_object arm_opp_qos = { + .requirements = { + LIST_HEAD_INIT(arm_opp_qos.requirements.list) + }, + /* + * No notifier on ARM opp qos request, since this won't actually + * do anything, except changing limits for cpufreq + */ + .name = "arm_opp", + /* Target value in % ARM OPP, note can be 125% */ + .default_value = 25, + .force_value = 0, + .target_value = ATOMIC_INIT(0), + .comparitor = max_compare +}; + +static struct prcmu_qos_object *prcmu_qos_array[] = { + &null_qos, + &ape_opp_qos, + &ddr_opp_qos, + &arm_opp_qos, +}; + +static DEFINE_MUTEX(prcmu_qos_mutex); +static DEFINE_SPINLOCK(prcmu_qos_lock); + +static bool ape_opp_forced_to_50_partly_25; + +static unsigned long cpufreq_opp_delay = HZ / 5; + +unsigned long prcmu_qos_get_cpufreq_opp_delay(void) +{ + return cpufreq_opp_delay; +} + +static struct notifier_block qos_delayed_cpufreq_notifier_block = { + .notifier_call = qos_delayed_cpufreq_notifier, +}; + +void prcmu_qos_set_cpufreq_opp_delay(unsigned long n) +{ + if (n == 0) { + cpufreq_unregister_notifier(&qos_delayed_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + prcmu_qos_update_requirement(PRCMU_QOS_DDR_OPP, "cpufreq", + PRCMU_QOS_DEFAULT_VALUE); + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "cpufreq", + PRCMU_QOS_DEFAULT_VALUE); + cpufreq_requirement_set = PRCMU_QOS_DEFAULT_VALUE; + cpufreq_requirement_queued = PRCMU_QOS_DEFAULT_VALUE; + } else if (cpufreq_opp_delay != 0) { + cpufreq_register_notifier(&qos_delayed_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + } + cpufreq_opp_delay = n; +} +#ifdef CONFIG_CPU_FREQ +static void update_cpu_limits(s32 extreme_value) +{ + int cpu; + struct cpufreq_policy policy; + int ret; + int min_freq, max_freq; + + for_each_online_cpu(cpu) { + ret = cpufreq_get_policy(&policy, cpu); + if (ret) { + pr_err("prcmu qos: get cpufreq policy failed (cpu%d)\n", + cpu); + continue; + } + + ret = dbx500_cpufreq_get_limits(cpu, extreme_value, + &min_freq, &max_freq); + if (ret) + continue; + /* + * cpufreq fw does not allow frequency change if + * "current min freq" > "new max freq" or + * "current max freq" < "new min freq". + * Thus the intermediate steps below. + */ + if (policy.min > max_freq) { + ret = cpufreq_update_freq(cpu, min_freq, policy.max); + if (ret) + pr_err("prcmu qos: update min cpufreq failed (1)\n"); + } + if (policy.max < min_freq) { + ret = cpufreq_update_freq(cpu, policy.min, max_freq); + if (ret) + pr_err("prcmu qos: update max cpufreq failed (2)\n"); + } + + ret = cpufreq_update_freq(cpu, min_freq, max_freq); + if (ret) + pr_err("prcmu qos: update max cpufreq failed (3)\n"); + } + +} +#else +static inline void update_cpu_limits(s32 extreme_value) { } +#endif +/* static helper function */ +static s32 max_compare(s32 v1, s32 v2) +{ + return max(v1, v2); +} + +static void update_target(int target) +{ + s32 extreme_value; + struct requirement_list *node; + unsigned long flags; + bool update = false; + u8 op; + + mutex_lock(&prcmu_qos_mutex); + + spin_lock_irqsave(&prcmu_qos_lock, flags); + extreme_value = prcmu_qos_array[target]->default_value; + + if (prcmu_qos_array[target]->force_value != 0) { + extreme_value = prcmu_qos_array[target]->force_value; + update = true; + } else { + list_for_each_entry(node, + &prcmu_qos_array[target]->requirements.list, + list) { + extreme_value = prcmu_qos_array[target]->comparitor( + extreme_value, node->value); + } + if (atomic_read(&prcmu_qos_array[target]->target_value) + != extreme_value) { + update = true; + atomic_set(&prcmu_qos_array[target]->target_value, + extreme_value); + pr_debug("prcmu qos: new target for qos %d is %d\n", + target, atomic_read( + &prcmu_qos_array[target]->target_value + )); + } + } + spin_unlock_irqrestore(&prcmu_qos_lock, flags); + + if (!update) + goto unlock_and_return; + + if (prcmu_qos_array[target]->notifiers) + blocking_notifier_call_chain(prcmu_qos_array[target]->notifiers, + (unsigned long)extreme_value, + NULL); + switch (target) { + case PRCMU_QOS_DDR_OPP: + switch (extreme_value) { + case 50: + op = DDR_50_OPP; + pr_debug("prcmu qos: set ddr opp to 50%%\n"); + break; + case 100: + op = DDR_100_OPP; + pr_debug("prcmu qos: set ddr opp to 100%%\n"); + break; + case 25: + /* 25% DDR OPP is not supported on 5500 */ + if (!cpu_is_u5500()) { + op = DDR_25_OPP; + pr_debug("prcmu qos: set ddr opp to 25%%\n"); + break; + } + default: + pr_err("prcmu qos: Incorrect ddr target value (%d)", + extreme_value); + goto unlock_and_return; + } + prcmu_set_ddr_opp(op); + prcmu_debug_ddr_opp_log(op); + break; + case PRCMU_QOS_APE_OPP: + switch (extreme_value) { + case 50: + op = APE_50_OPP; + pr_debug("prcmu qos: set ape opp to 50%%\n"); + break; + case 100: + op = APE_100_OPP; + pr_debug("prcmu qos: set ape opp to 100%%\n"); + break; + default: + pr_err("prcmu qos: Incorrect ape target value (%d)", + extreme_value); + goto unlock_and_return; + } + + if (!ape_opp_forced_to_50_partly_25) + (void)prcmu_set_ape_opp(op); + prcmu_debug_ape_opp_log(op); + break; + case PRCMU_QOS_ARM_OPP: + { + mutex_unlock(&prcmu_qos_mutex); + /* + * We can't hold the mutex since changing cpufreq + * will trigger an prcmu fw callback. + */ + update_cpu_limits(extreme_value); + /* Return since the lock is unlocked */ + return; + + break; + } + default: + pr_err("prcmu qos: Incorrect target\n"); + break; + } + +unlock_and_return: + mutex_unlock(&prcmu_qos_mutex); +} + +void prcmu_qos_force_opp(int prcmu_qos_class, s32 i) +{ + prcmu_qos_array[prcmu_qos_class]->force_value = i; + update_target(prcmu_qos_class); +} + +void prcmu_qos_voice_call_override(bool enable) +{ + u8 op; + + mutex_lock(&prcmu_qos_mutex); + + ape_opp_forced_to_50_partly_25 = enable; + + if (enable) { + (void)prcmu_set_ape_opp(APE_50_PARTLY_25_OPP); + goto unlock_and_return; + } + + /* Disable: set the OPP according to the current target value. */ + switch (atomic_read( + &prcmu_qos_array[PRCMU_QOS_APE_OPP]->target_value)) { + case 50: + op = APE_50_OPP; + break; + case 100: + op = APE_100_OPP; + break; + default: + goto unlock_and_return; + } + + (void)prcmu_set_ape_opp(op); + +unlock_and_return: + mutex_unlock(&prcmu_qos_mutex); +} + +/** + * prcmu_qos_requirement - returns current prcmu qos expectation + * @prcmu_qos_class: identification of which qos value is requested + * + * This function returns the current target value in an atomic manner. + */ +int prcmu_qos_requirement(int prcmu_qos_class) +{ + return atomic_read(&prcmu_qos_array[prcmu_qos_class]->target_value); +} +EXPORT_SYMBOL_GPL(prcmu_qos_requirement); + +/** + * prcmu_qos_add_requirement - inserts new qos request into the list + * @prcmu_qos_class: identifies which list of qos request to us + * @name: identifies the request + * @value: defines the qos request + * + * This function inserts a new entry in the prcmu_qos_class list of requested + * qos performance characteristics. It recomputes the aggregate QoS + * expectations for the prcmu_qos_class of parameters. + */ +int prcmu_qos_add_requirement(int prcmu_qos_class, char *name, s32 value) +{ + struct requirement_list *dep; + unsigned long flags; + + dep = kzalloc(sizeof(struct requirement_list), GFP_KERNEL); + if (dep == NULL) + return -ENOMEM; + + if (value == PRCMU_QOS_DEFAULT_VALUE) + dep->value = prcmu_qos_array[prcmu_qos_class]->default_value; + else + dep->value = value; + dep->name = kstrdup(name, GFP_KERNEL); + if (!dep->name) + goto cleanup; + + spin_lock_irqsave(&prcmu_qos_lock, flags); + list_add(&dep->list, + &prcmu_qos_array[prcmu_qos_class]->requirements.list); + spin_unlock_irqrestore(&prcmu_qos_lock, flags); + update_target(prcmu_qos_class); + + return 0; + +cleanup: + kfree(dep); + return -ENOMEM; +} +EXPORT_SYMBOL_GPL(prcmu_qos_add_requirement); + +/** + * prcmu_qos_update_requirement - modifies an existing qos request + * @prcmu_qos_class: identifies which list of qos request to us + * @name: identifies the request + * @value: defines the qos request + * + * Updates an existing qos requirement for the prcmu_qos_class of parameters + * along with updating the target prcmu_qos_class value. + * + * If the named request isn't in the list then no change is made. + */ +int prcmu_qos_update_requirement(int prcmu_qos_class, char *name, s32 new_value) +{ + unsigned long flags; + struct requirement_list *node; + int pending_update = 0; + + spin_lock_irqsave(&prcmu_qos_lock, flags); + list_for_each_entry(node, + &prcmu_qos_array[prcmu_qos_class]->requirements.list, list) { + if (strcmp(node->name, name) == 0) { + if (new_value == PRCMU_QOS_DEFAULT_VALUE) + node->value = + prcmu_qos_array[prcmu_qos_class]->default_value; + else + node->value = new_value; + pending_update = 1; + break; + } + } + spin_unlock_irqrestore(&prcmu_qos_lock, flags); + if (pending_update) + update_target(prcmu_qos_class); + + return 0; +} +EXPORT_SYMBOL_GPL(prcmu_qos_update_requirement); + +/** + * prcmu_qos_remove_requirement - modifies an existing qos request + * @prcmu_qos_class: identifies which list of qos request to us + * @name: identifies the request + * + * Will remove named qos request from prcmu_qos_class list of parameters and + * recompute the current target value for the prcmu_qos_class. + */ +void prcmu_qos_remove_requirement(int prcmu_qos_class, char *name) +{ + unsigned long flags; + struct requirement_list *node; + int pending_update = 0; + + spin_lock_irqsave(&prcmu_qos_lock, flags); + list_for_each_entry(node, + &prcmu_qos_array[prcmu_qos_class]->requirements.list, list) { + if (strcmp(node->name, name) == 0) { + kfree(node->name); + list_del(&node->list); + kfree(node); + pending_update = 1; + break; + } + } + spin_unlock_irqrestore(&prcmu_qos_lock, flags); + if (pending_update) + update_target(prcmu_qos_class); +} +EXPORT_SYMBOL_GPL(prcmu_qos_remove_requirement); + +/** + * prcmu_qos_add_notifier - sets notification entry for changes to target value + * @prcmu_qos_class: identifies which qos target changes should be notified. + * @notifier: notifier block managed by caller. + * + * will register the notifier into a notification chain that gets called + * upon changes to the prcmu_qos_class target value. + */ +int prcmu_qos_add_notifier(int prcmu_qos_class, struct notifier_block *notifier) +{ + int retval = -EINVAL; + + if (prcmu_qos_array[prcmu_qos_class]->notifiers) + retval = blocking_notifier_chain_register( + prcmu_qos_array[prcmu_qos_class]->notifiers, notifier); + + return retval; +} +EXPORT_SYMBOL_GPL(prcmu_qos_add_notifier); + +/** + * prcmu_qos_remove_notifier - deletes notification entry from chain. + * @prcmu_qos_class: identifies which qos target changes are notified. + * @notifier: notifier block to be removed. + * + * will remove the notifier from the notification chain that gets called + * upon changes to the prcmu_qos_class target value. + */ +int prcmu_qos_remove_notifier(int prcmu_qos_class, + struct notifier_block *notifier) +{ + int retval = -EINVAL; + if (prcmu_qos_array[prcmu_qos_class]->notifiers) + retval = blocking_notifier_chain_unregister( + prcmu_qos_array[prcmu_qos_class]->notifiers, notifier); + + return retval; +} +EXPORT_SYMBOL_GPL(prcmu_qos_remove_notifier); + +#define USER_QOS_NAME_LEN 32 + +static int prcmu_qos_power_open(struct inode *inode, struct file *filp, + long prcmu_qos_class) +{ + int ret; + char name[USER_QOS_NAME_LEN]; + + filp->private_data = (void *)prcmu_qos_class; + snprintf(name, USER_QOS_NAME_LEN, "file_%08x", (unsigned int)filp); + ret = prcmu_qos_add_requirement(prcmu_qos_class, name, + PRCMU_QOS_DEFAULT_VALUE); + if (ret >= 0) + return 0; + + return -EPERM; +} + + +static int prcmu_qos_ape_power_open(struct inode *inode, struct file *filp) +{ + return prcmu_qos_power_open(inode, filp, PRCMU_QOS_APE_OPP); +} + +static int prcmu_qos_ddr_power_open(struct inode *inode, struct file *filp) +{ + return prcmu_qos_power_open(inode, filp, PRCMU_QOS_DDR_OPP); +} + +static int prcmu_qos_power_release(struct inode *inode, struct file *filp) +{ + int prcmu_qos_class; + char name[USER_QOS_NAME_LEN]; + + prcmu_qos_class = (long)filp->private_data; + snprintf(name, USER_QOS_NAME_LEN, "file_%08x", (unsigned int)filp); + prcmu_qos_remove_requirement(prcmu_qos_class, name); + + return 0; +} + +static ssize_t prcmu_qos_power_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + s32 value; + int prcmu_qos_class; + char name[USER_QOS_NAME_LEN]; + + prcmu_qos_class = (long)filp->private_data; + if (count != sizeof(s32)) + return -EINVAL; + if (copy_from_user(&value, buf, sizeof(s32))) + return -EFAULT; + snprintf(name, USER_QOS_NAME_LEN, "file_%08x", (unsigned int)filp); + prcmu_qos_update_requirement(prcmu_qos_class, name, value); + + return sizeof(s32); +} + +/* Functions to provide QoS to user space */ +static const struct file_operations prcmu_qos_ape_power_fops = { + .write = prcmu_qos_power_write, + .open = prcmu_qos_ape_power_open, + .release = prcmu_qos_power_release, +}; + +/* Functions to provide QoS to user space */ +static const struct file_operations prcmu_qos_ddr_power_fops = { + .write = prcmu_qos_power_write, + .open = prcmu_qos_ddr_power_open, + .release = prcmu_qos_power_release, +}; + +static int register_prcmu_qos_misc(struct prcmu_qos_object *qos, + const struct file_operations *fops) +{ + qos->prcmu_qos_power_miscdev.minor = MISC_DYNAMIC_MINOR; + qos->prcmu_qos_power_miscdev.name = qos->name; + qos->prcmu_qos_power_miscdev.fops = fops; + + return misc_register(&qos->prcmu_qos_power_miscdev); +} + +static void qos_delayed_work_up_fn(struct work_struct *work) +{ + prcmu_qos_update_requirement(PRCMU_QOS_DDR_OPP, "cpufreq", 100); + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "cpufreq", 100); + cpufreq_requirement_set = 100; +} + +static void qos_delayed_work_down_fn(struct work_struct *work) +{ + prcmu_qos_update_requirement(PRCMU_QOS_DDR_OPP, "cpufreq", + PRCMU_QOS_DEFAULT_VALUE); + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "cpufreq", + PRCMU_QOS_DEFAULT_VALUE); + cpufreq_requirement_set = PRCMU_QOS_DEFAULT_VALUE; +} + +static DECLARE_DELAYED_WORK(qos_delayed_work_up, qos_delayed_work_up_fn); +static DECLARE_DELAYED_WORK(qos_delayed_work_down, qos_delayed_work_down_fn); + +static int qos_delayed_cpufreq_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct cpufreq_freqs *freq = data; + s32 new_ddr_target; + + /* Only react once per transition and only for one core, e.g. core 0 */ + if (event != CPUFREQ_POSTCHANGE || freq->cpu != 0) + return 0; + + /* + * APE and DDR OPP are always handled together in this solution. + * Hence no need to check both DDR and APE opp in the code below. + */ + + /* Which DDR OPP are we aiming for? */ + if (freq->new > ARM_THRESHOLD_FREQ) + new_ddr_target = 100; + else + new_ddr_target = PRCMU_QOS_DEFAULT_VALUE; + + if (new_ddr_target == cpufreq_requirement_queued) { + /* + * We're already at, or going to, the target requirement. + * This is only a fluctuation within the interval + * corresponding to the same DDR requirement. + */ + return 0; + } + cpufreq_requirement_queued = new_ddr_target; + + if (freq->new > ARM_THRESHOLD_FREQ) { + cancel_delayed_work_sync(&qos_delayed_work_down); + /* + * Only schedule this requirement if it is not the current + * one. + */ + if (new_ddr_target != cpufreq_requirement_set) + schedule_delayed_work(&qos_delayed_work_up, + cpufreq_opp_delay); + } else { + cancel_delayed_work_sync(&qos_delayed_work_up); + /* + * Only schedule this requirement if it is not the current + * one. + */ + if (new_ddr_target != cpufreq_requirement_set) + schedule_delayed_work(&qos_delayed_work_down, + cpufreq_opp_delay); + } + + return 0; +} + +static int __init prcmu_qos_power_init(void) +{ + int ret; + + /* 25% DDR OPP is not supported on u5500 */ + if (cpu_is_u5500()) + ddr_opp_qos.default_value = 50; + + ret = register_prcmu_qos_misc(&ape_opp_qos, &prcmu_qos_ape_power_fops); + if (ret < 0) { + pr_err("prcmu ape qos: setup failed\n"); + return ret; + } + + ret = register_prcmu_qos_misc(&ddr_opp_qos, &prcmu_qos_ddr_power_fops); + if (ret < 0) { + pr_err("prcmu ddr qos: setup failed\n"); + return ret; + } + + prcmu_qos_add_requirement(PRCMU_QOS_DDR_OPP, "cpufreq", + PRCMU_QOS_DEFAULT_VALUE); + prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, "cpufreq", + PRCMU_QOS_DEFAULT_VALUE); + cpufreq_requirement_set = PRCMU_QOS_DEFAULT_VALUE; + cpufreq_requirement_queued = PRCMU_QOS_DEFAULT_VALUE; + + cpufreq_register_notifier(&qos_delayed_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + + return ret; +} + +late_initcall(prcmu_qos_power_init); diff --git a/arch/arm/mach-ux500/pm/runtime.c b/arch/arm/mach-ux500/pm/runtime.c new file mode 100644 index 00000000000..3d822d955cd --- /dev/null +++ b/arch/arm/mach-ux500/pm/runtime.c @@ -0,0 +1,353 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Rabin Vincent <rabin.vincent@stericsson> for ST-Ericsson + * + * Based on: + * Runtime PM support code for SuperH Mobile ARM + * Copyright (C) 2009-2010 Magnus Damm + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/pm_runtime.h> +#include <linux/platform_device.h> +#include <linux/amba/bus.h> +#include <linux/regulator/dbx500-prcmu.h> +#include <linux/clk.h> +#include <plat/pincfg.h> + +#include "../pins.h" + +#ifdef CONFIG_PM_RUNTIME +#define BIT_ONCE 0 +#define BIT_ACTIVE 1 +#define BIT_ENABLED 2 + +struct pm_runtime_data { + unsigned long flags; + struct ux500_regulator *regulator; + struct ux500_pins *pins; +}; + +static void __devres_release(struct device *dev, void *res) +{ + struct pm_runtime_data *prd = res; + + dev_dbg(dev, "__devres_release()\n"); + + if (test_bit(BIT_ENABLED, &prd->flags)) { + if (prd->pins) + ux500_pins_disable(prd->pins); + if (prd->regulator) + ux500_regulator_atomic_disable(prd->regulator); + } + + if (test_bit(BIT_ACTIVE, &prd->flags)) { + if (prd->pins) + ux500_pins_put(prd->pins); + if (prd->regulator) + ux500_regulator_put(prd->regulator); + } +} + +static struct pm_runtime_data *__to_prd(struct device *dev) +{ + return devres_find(dev, __devres_release, NULL, NULL); +} + +static void platform_pm_runtime_init(struct device *dev, + struct pm_runtime_data *prd) +{ + prd->pins = ux500_pins_get(dev_name(dev)); + + prd->regulator = ux500_regulator_get(dev); + if (IS_ERR(prd->regulator)) + prd->regulator = NULL; + + if (prd->pins || prd->regulator) { + dev_info(dev, "managed by runtime pm: %s%s\n", + prd->pins ? "pins " : "", + prd->regulator ? "regulator " : ""); + + set_bit(BIT_ACTIVE, &prd->flags); + } +} + +static void platform_pm_runtime_bug(struct device *dev, + struct pm_runtime_data *prd) +{ + if (prd && !test_and_set_bit(BIT_ONCE, &prd->flags)) + dev_err(dev, "runtime pm suspend before resume\n"); +} + +static void platform_pm_runtime_used(struct device *dev, + struct pm_runtime_data *prd) +{ + if (prd) + set_bit(BIT_ONCE, &prd->flags); +} + +static int ux500_pd_runtime_idle(struct device *dev) +{ + return pm_runtime_suspend(dev); +} + +static int ux500_pd_runtime_suspend(struct device *dev) +{ + int ret; + struct pm_runtime_data *prd = __to_prd(dev); + + dev_vdbg(dev, "%s()\n", __func__); + + platform_pm_runtime_bug(dev, prd); + + ret = pm_generic_runtime_suspend(dev); + if (ret) + return ret; + + if (prd && test_bit(BIT_ACTIVE, &prd->flags)) { + + if (prd->pins) + ux500_pins_disable(prd->pins); + + if (prd->regulator) + ux500_regulator_atomic_disable(prd->regulator); + + clear_bit(BIT_ENABLED, &prd->flags); + } + + return 0; +} + +static int ux500_pd_runtime_resume(struct device *dev) +{ + struct pm_runtime_data *prd = __to_prd(dev); + + dev_vdbg(dev, "%s()\n", __func__); + + platform_pm_runtime_used(dev, prd); + + if (prd && test_bit(BIT_ACTIVE, &prd->flags)) { + if (prd->pins) + ux500_pins_enable(prd->pins); + + if (prd->regulator) + ux500_regulator_atomic_enable(prd->regulator); + + set_bit(BIT_ENABLED, &prd->flags); + + } + return pm_generic_runtime_resume(dev); +} + +static int ux500_pd_suspend_noirq(struct device *dev) +{ + struct pm_runtime_data *prd = __to_prd(dev); + + dev_vdbg(dev, "%s()\n", __func__); + + /* Only handle devices that use runtime pm */ + if (!prd || !test_bit(BIT_ONCE, &prd->flags)) + return 0; + + /* Already is runtime suspended? Nothing to do. */ + if (pm_runtime_suspended(dev)) + return 0; + + /* + * We get here only if the device was not runtime suspended for some + * reason. We still need to do the power save stuff when going into + * suspend, so force it here. + */ + return ux500_pd_runtime_suspend(dev); +} + +static int ux500_pd_resume_noirq(struct device *dev) +{ + struct pm_runtime_data *prd = __to_prd(dev); + + dev_vdbg(dev, "%s()\n", __func__); + + /* Only handle devices that use runtime pm */ + if (!prd || !test_bit(BIT_ONCE, &prd->flags)) + return 0; + + /* + * Already was runtime suspended? No need to resume here, runtime + * resume will take care of it. + */ + if (pm_runtime_suspended(dev)) + return 0; + + /* + * We get here only if the device was not runtime suspended, + * but we forced it down in suspend_noirq above. Bring it + * up since pm-runtime thinks it is not suspended. + */ + return ux500_pd_runtime_resume(dev); +} + +static int ux500_pd_bus_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct device *dev = data; + struct pm_runtime_data *prd; + + dev_dbg(dev, "%s() %ld !\n", __func__, action); + + if (action == BUS_NOTIFY_BIND_DRIVER) { + prd = devres_alloc(__devres_release, sizeof(*prd), GFP_KERNEL); + if (prd) { + devres_add(dev, prd); + platform_pm_runtime_init(dev, prd); + } else + dev_err(dev, "unable to alloc memory for runtime pm\n"); + } + + return 0; +} + +#else /* CONFIG_PM_RUNTIME */ + +#define ux500_pd_suspend_noirq NULL +#define ux500_pd_resume_noirq NULL +#define ux500_pd_runtime_suspend NULL +#define ux500_pd_runtime_resume NULL + +static int ux500_pd_bus_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct ux500_regulator *regulator = NULL; + struct ux500_pins *pins = NULL; + struct device *dev = data; + const char *onoff = NULL; + + dev_dbg(dev, "%s() %ld !\n", __func__, action); + + switch (action) { + case BUS_NOTIFY_BIND_DRIVER: + pins = ux500_pins_get(dev_name(dev)); + if (pins) { + ux500_pins_enable(pins); + ux500_pins_put(pins); + } + + regulator = ux500_regulator_get(dev); + if (IS_ERR(regulator)) + regulator = NULL; + else { + ux500_regulator_atomic_enable(regulator); + ux500_regulator_put(regulator); + } + + onoff = "on"; + break; + case BUS_NOTIFY_UNBOUND_DRIVER: + pins = ux500_pins_get(dev_name(dev)); + if (pins) { + ux500_pins_disable(pins); + ux500_pins_put(pins); + } + + regulator = ux500_regulator_get(dev); + if (IS_ERR(regulator)) + regulator = NULL; + else { + ux500_regulator_atomic_disable(regulator); + ux500_regulator_put(regulator); + } + + onoff = "off"; + break; + } + + if (pins || regulator) { + dev_info(dev, "runtime pm disabled, forced %s: %s%s\n", + onoff, + pins ? "pins " : "", + regulator ? "regulator " : ""); + } + + return 0; +} + +#endif /* CONFIG_PM_RUNTIME */ + +struct dev_pm_domain ux500_amba_dev_power_domain = { + .ops = { + /* USE_AMBA_PM_SLEEP_OPS minus the two we replace */ + .prepare = amba_pm_prepare, + .complete = amba_pm_complete, + .suspend = amba_pm_suspend, + .resume = amba_pm_resume, + .freeze = amba_pm_freeze, + .thaw = amba_pm_thaw, + .poweroff = amba_pm_poweroff, + .restore = amba_pm_restore, + .freeze_noirq = amba_pm_freeze_noirq, + .thaw_noirq = amba_pm_thaw_noirq, + .poweroff_noirq = amba_pm_poweroff_noirq, + .restore_noirq = amba_pm_restore_noirq, + + .suspend_noirq = ux500_pd_suspend_noirq, + .resume_noirq = ux500_pd_resume_noirq, + .runtime_idle = ux500_pd_runtime_idle, + .runtime_suspend = ux500_pd_runtime_suspend, + .runtime_resume = ux500_pd_runtime_resume, + }, +}; + +struct dev_pm_domain ux500_dev_power_domain = { + .ops = { + /* USE_PLATFORM_PM_SLEEP_OPS minus the two we replace */ + .prepare = platform_pm_prepare, + .complete = platform_pm_complete, + .suspend = platform_pm_suspend, + .resume = platform_pm_resume, + .freeze = platform_pm_freeze, + .thaw = platform_pm_thaw, + .poweroff = platform_pm_poweroff, + .restore = platform_pm_restore, + .freeze_noirq = platform_pm_freeze_noirq, + .thaw_noirq = platform_pm_thaw_noirq, + .poweroff_noirq = platform_pm_poweroff_noirq, + .restore_noirq = platform_pm_restore_noirq, + + .suspend_noirq = ux500_pd_suspend_noirq, + .resume_noirq = ux500_pd_resume_noirq, + .runtime_idle = ux500_pd_runtime_idle, + .runtime_suspend = ux500_pd_runtime_suspend, + .runtime_resume = ux500_pd_runtime_resume, + }, +}; + +static struct notifier_block ux500_pd_platform_notifier = { + .notifier_call = ux500_pd_bus_notify, +}; + +static struct notifier_block ux500_pd_amba_notifier = { + .notifier_call = ux500_pd_bus_notify, +}; + +static int __init ux500_pm_runtime_platform_init(void) +{ + bus_register_notifier(&platform_bus_type, &ux500_pd_platform_notifier); + return 0; +} +core_initcall(ux500_pm_runtime_platform_init); + +/* + * The amba bus itself gets registered in a core_initcall, so we can't use + * that. + */ +static int __init ux500_pm_runtime_amba_init(void) +{ + bus_register_notifier(&amba_bustype, &ux500_pd_amba_notifier); + return 0; +} +arch_initcall(ux500_pm_runtime_amba_init); diff --git a/arch/arm/mach-ux500/pm/scu.h b/arch/arm/mach-ux500/pm/scu.h new file mode 100644 index 00000000000..a09e86a9d3c --- /dev/null +++ b/arch/arm/mach-ux500/pm/scu.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2009 ST-Ericsson SA + * + * 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. + */ + +#ifndef __ASMARM_ARCH_SCU_H +#define __ASMARM_ARCH_SCU_H + +#include <mach/hardware.h> + +#define SCU_BASE U8500_SCU_BASE +/* + * * SCU registers + * */ +#define SCU_CTRL 0x00 +#define SCU_CONFIG 0x04 +#define SCU_CPU_STATUS 0x08 +#define SCU_INVALIDATE 0x0c +#define SCU_FPGA_REVISION 0x10 + +#endif diff --git a/arch/arm/mach-ux500/pm/suspend.c b/arch/arm/mach-ux500/pm/suspend.c new file mode 100644 index 00000000000..d64faa99a3a --- /dev/null +++ b/arch/arm/mach-ux500/pm/suspend.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * + * Authors: Rickard Andersson <rickard.andersson@stericsson.com>, + * Jonas Aaberg <jonas.aberg@stericsson.com>, + * Sundar Iyer for ST-Ericsson. + */ + +#include <linux/suspend.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/gpio/nomadik.h> +#include <linux/regulator/ab8500-debug.h> +#include <linux/regulator/dbx500-prcmu.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include <mach/context.h> +#include <mach/pm.h> + +#include "suspend_dbg.h" + +static void (*pins_suspend_force)(void); +static void (*pins_suspend_force_mux)(void); + +void suspend_set_pins_force_fn(void (*force)(void), void (*force_mux)(void)) +{ + pins_suspend_force = force; + pins_suspend_force_mux = force_mux; +} + +static atomic_t block_sleep = ATOMIC_INIT(0); + +void suspend_block_sleep(void) +{ + atomic_inc(&block_sleep); +} + +void suspend_unblock_sleep(void) +{ + atomic_dec(&block_sleep); +} + +static bool sleep_is_blocked(void) +{ + return (atomic_read(&block_sleep) != 0); +} + +static int suspend(bool do_deepsleep) +{ + bool pins_force = pins_suspend_force_mux && pins_suspend_force; + int ret = 0; + + if (sleep_is_blocked()) { + pr_info("suspend/resume: interrupted by modem.\n"); + return -EBUSY; + } + + nmk_gpio_clocks_enable(); + + ux500_suspend_dbg_add_wake_on_uart(); + + nmk_gpio_wakeups_suspend(); + + /* configure the prcm for a sleep wakeup */ + prcmu_enable_wakeups(PRCMU_WAKEUP(ABB)); + + context_vape_save(); + + if (pins_force) { + /* + * Save GPIO settings before applying power save + * settings + */ + context_gpio_save(); + + /* Apply GPIO power save mux settings */ + context_gpio_mux_safe_switch(true); + pins_suspend_force_mux(); + context_gpio_mux_safe_switch(false); + + /* Apply GPIO power save settings */ + pins_suspend_force(); + } + + ux500_pm_gic_decouple(); + + if (ux500_pm_gic_pending_interrupt()) { + pr_info("suspend/resume: pending interrupt\n"); + + /* Recouple GIC with the interrupt bus */ + ux500_pm_gic_recouple(); + ret = -EBUSY; + + goto exit; + } + ux500_pm_prcmu_set_ioforce(true); + + if (do_deepsleep) { + context_varm_save_common(); + context_varm_save_core(); + context_gic_dist_disable_unneeded_irqs(); + context_save_cpu_registers(); + + /* + * Due to we have only 100us between requesting a powerstate + * and wfi, we clean the cache before as well to assure the + * final cache clean before wfi has as little as possible to + * do. + */ + context_clean_l1_cache_all(); + + (void) prcmu_set_power_state(PRCMU_AP_DEEP_SLEEP, + false, false); + context_save_to_sram_and_wfi(true); + + context_restore_cpu_registers(); + context_varm_restore_core(); + context_varm_restore_common(); + + } else { + + context_clean_l1_cache_all(); + (void) prcmu_set_power_state(APEXECUTE_TO_APSLEEP, + false, false); + dsb(); + __asm__ __volatile__("wfi\n\t" : : : "memory"); + } + + context_vape_restore(); + + /* If GPIO woke us up then save the pins that caused the wake up */ + ux500_pm_gpio_save_wake_up_status(); + + ux500_suspend_dbg_sleep_status(do_deepsleep); + + /* APE was turned off, restore IO ring */ + ux500_pm_prcmu_set_ioforce(false); + +exit: + if (pins_force) { + /* Restore gpio settings */ + context_gpio_mux_safe_switch(true); + context_gpio_restore_mux(); + context_gpio_mux_safe_switch(false); + context_gpio_restore(); + } + + /* This is what cpuidle wants */ + prcmu_enable_wakeups(PRCMU_WAKEUP(ARM) | PRCMU_WAKEUP(RTC) | + PRCMU_WAKEUP(ABB)); + + nmk_gpio_wakeups_resume(); + + ux500_suspend_dbg_remove_wake_on_uart(); + + nmk_gpio_clocks_disable(); + + return ret; +} + +static int ux500_suspend_enter(suspend_state_t state) +{ + if (ux500_suspend_enabled()) { + if (ux500_suspend_deepsleep_enabled() && + state == PM_SUSPEND_MEM) + return suspend(true); + if (ux500_suspend_sleep_enabled()) + return suspend(false); + } + + ux500_suspend_dbg_add_wake_on_uart(); + /* + * Set IOFORCE in order to wake on GPIO the same way + * as in deeper sleep. + * (U5500 is not ready for IOFORCE) + */ + if (!cpu_is_u5500()) + ux500_pm_prcmu_set_ioforce(true); + + dsb(); + __asm__ __volatile__("wfi\n\t" : : : "memory"); + + if (!cpu_is_u5500()) + ux500_pm_prcmu_set_ioforce(false); + ux500_suspend_dbg_remove_wake_on_uart(); + + return 0; +} + +static int ux500_suspend_valid(suspend_state_t state) +{ + return state == PM_SUSPEND_MEM || state == PM_SUSPEND_STANDBY; +} + +static int ux500_suspend_prepare_late(void) +{ + /* ESRAM to retention instead of OFF until ROM is fixed */ + (void) prcmu_config_esram0_deep_sleep(ESRAM0_DEEP_SLEEP_STATE_RET); + ab8500_regulator_debug_force(); + ux500_regulator_suspend_debug(); + return 0; +} + +static void ux500_suspend_wake(void) +{ + ux500_regulator_resume_debug(); + ab8500_regulator_debug_restore(); + (void) prcmu_config_esram0_deep_sleep(ESRAM0_DEEP_SLEEP_STATE_RET); +} + +static int ux500_suspend_begin(suspend_state_t state) +{ + (void) prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, + "suspend", 100); + return ux500_suspend_dbg_begin(state); +} + +static void ux500_suspend_end(void) +{ + (void) prcmu_qos_update_requirement(PRCMU_QOS_ARM_OPP, + "suspend", 25); +} + +static struct platform_suspend_ops ux500_suspend_ops = { + .enter = ux500_suspend_enter, + .valid = ux500_suspend_valid, + .prepare_late = ux500_suspend_prepare_late, + .wake = ux500_suspend_wake, + .begin = ux500_suspend_begin, + .end = ux500_suspend_end, +}; + +static __init int ux500_suspend_init(void) +{ + ux500_suspend_dbg_init(); + prcmu_qos_add_requirement(PRCMU_QOS_ARM_OPP, "suspend", 25); + suspend_set_ops(&ux500_suspend_ops); + return 0; +} +device_initcall(ux500_suspend_init); diff --git a/arch/arm/mach-ux500/pm/suspend_dbg.c b/arch/arm/mach-ux500/pm/suspend_dbg.c new file mode 100644 index 00000000000..cd058bad91e --- /dev/null +++ b/arch/arm/mach-ux500/pm/suspend_dbg.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Rickard Andersson <rickard.andersson@stericsson.com>, + * Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + */ +#include <linux/kernel.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/suspend.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> + +#include <mach/pm.h> + +#ifdef CONFIG_UX500_SUSPEND_STANDBY +static u32 sleep_enabled = 1; +#else +static u32 sleep_enabled; +#endif + +#ifdef CONFIG_UX500_SUSPEND_MEM +static u32 deepsleep_enabled = 1; +#else +static u32 deepsleep_enabled; +#endif + +static u32 suspend_enabled = 1; + +static u32 deepsleeps_done; +static u32 deepsleeps_failed; +static u32 sleeps_done; +static u32 sleeps_failed; +static u32 suspend_count; + +#ifdef CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART +void ux500_suspend_dbg_add_wake_on_uart(void) +{ + irq_set_irq_wake(GPIO_TO_IRQ(CONFIG_UX500_CONSOLE_UART_GPIO_PIN), 1); + irq_set_irq_type(GPIO_TO_IRQ(CONFIG_UX500_CONSOLE_UART_GPIO_PIN), + IRQ_TYPE_EDGE_BOTH); +} + +void ux500_suspend_dbg_remove_wake_on_uart(void) +{ + irq_set_irq_wake(GPIO_TO_IRQ(CONFIG_UX500_CONSOLE_UART_GPIO_PIN), 0); +} +#endif + +bool ux500_suspend_enabled(void) +{ + return suspend_enabled != 0; +} + +bool ux500_suspend_sleep_enabled(void) +{ + return sleep_enabled != 0; +} + +bool ux500_suspend_deepsleep_enabled(void) +{ + return deepsleep_enabled != 0; +} + +void ux500_suspend_dbg_sleep_status(bool is_deepsleep) +{ + enum prcmu_idle_stat prcmu_status; + + prcmu_status = ux500_pm_prcmu_idle_stat(); + + if (is_deepsleep) { + pr_info("Returning from ApDeepSleep. PRCMU ret: 0x%x - %s\n", + prcmu_status, + prcmu_status == DEEP_SLEEP_OK ? "Success" : "Fail!"); + if (prcmu_status == DEEP_SLEEP_OK) + deepsleeps_done++; + else + deepsleeps_failed++; + } else { + pr_info("Returning from ApSleep. PRCMU ret: 0x%x - %s\n", + prcmu_status, + prcmu_status == SLEEP_OK ? "Success" : "Fail!"); + if (prcmu_status == SLEEP_OK) + sleeps_done++; + else + sleeps_failed++; + } +} + +int ux500_suspend_dbg_begin(suspend_state_t state) +{ + suspend_count++; + return 0; +} + +void ux500_suspend_dbg_init(void) +{ + struct dentry *suspend_dir; + struct dentry *file; + + suspend_dir = debugfs_create_dir("suspend", NULL); + if (IS_ERR_OR_NULL(suspend_dir)) + return; + + file = debugfs_create_bool("sleep", S_IWUGO | S_IRUGO, + suspend_dir, + &sleep_enabled); + if (IS_ERR_OR_NULL(file)) + goto error; + + file = debugfs_create_bool("deepsleep", S_IWUGO | S_IRUGO, + suspend_dir, + &deepsleep_enabled); + if (IS_ERR_OR_NULL(file)) + goto error; + + file = debugfs_create_bool("enable", S_IWUGO | S_IRUGO, + suspend_dir, + &suspend_enabled); + if (IS_ERR_OR_NULL(file)) + goto error; + + file = debugfs_create_u32("count", S_IRUGO, + suspend_dir, + &suspend_count); + if (IS_ERR_OR_NULL(file)) + goto error; + + file = debugfs_create_u32("sleep_count", S_IRUGO, + suspend_dir, + &sleeps_done); + if (IS_ERR_OR_NULL(file)) + goto error; + + file = debugfs_create_u32("deepsleep_count", S_IRUGO, + suspend_dir, + &deepsleeps_done); + if (IS_ERR_OR_NULL(file)) + goto error; + + + file = debugfs_create_u32("sleep_failed", S_IRUGO, + suspend_dir, + &sleeps_failed); + if (IS_ERR_OR_NULL(file)) + goto error; + + file = debugfs_create_u32("deepsleep_failed", S_IRUGO, + suspend_dir, + &deepsleeps_failed); + if (IS_ERR_OR_NULL(file)) + goto error; + + return; +error: + if (!IS_ERR_OR_NULL(suspend_dir)) + debugfs_remove_recursive(suspend_dir); +} diff --git a/arch/arm/mach-ux500/pm/suspend_dbg.h b/arch/arm/mach-ux500/pm/suspend_dbg.h new file mode 100644 index 00000000000..29bfec7e269 --- /dev/null +++ b/arch/arm/mach-ux500/pm/suspend_dbg.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + */ + +#ifndef UX500_SUSPEND_DBG_H +#define UX500_SUSPEND_DBG_H + +#include <linux/kernel.h> +#include <linux/suspend.h> + +#ifdef CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART +void ux500_suspend_dbg_add_wake_on_uart(void); +void ux500_suspend_dbg_remove_wake_on_uart(void); +#else +static inline void ux500_suspend_dbg_add_wake_on_uart(void) { } +static inline void ux500_suspend_dbg_remove_wake_on_uart(void) { } +#endif + +#ifdef CONFIG_UX500_SUSPEND_DBG +bool ux500_suspend_enabled(void); +bool ux500_suspend_sleep_enabled(void); +bool ux500_suspend_deepsleep_enabled(void); +void ux500_suspend_dbg_sleep_status(bool is_deepsleep); +void ux500_suspend_dbg_init(void); +int ux500_suspend_dbg_begin(suspend_state_t state); + +#else +static inline bool ux500_suspend_enabled(void) +{ + return true; +} +static inline bool ux500_suspend_sleep_enabled(void) +{ +#ifdef CONFIG_UX500_SUSPEND_STANDBY + return true; +#else + return false; +#endif +} +static inline bool ux500_suspend_deepsleep_enabled(void) +{ +#ifdef CONFIG_UX500_SUSPEND_MEM + return true; +#else + return false; +#endif +} +static inline void ux500_suspend_dbg_sleep_status(bool is_deepsleep) { } +static inline void ux500_suspend_dbg_init(void) { } + +static inline int ux500_suspend_dbg_begin(suspend_state_t state) +{ + return 0; +} + +#endif + +#endif diff --git a/arch/arm/mach-ux500/pm/timer.c b/arch/arm/mach-ux500/pm/timer.c new file mode 100644 index 00000000000..79652a6af23 --- /dev/null +++ b/arch/arm/mach-ux500/pm/timer.c @@ -0,0 +1,189 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + * License Terms: GNU General Public License v2 + * + * The RTC timer block is a ST Microelectronics variant of ARM PL031. + * Clockwatch part is the same as PL031, while the timer part is only + * present on the ST Microelectronics variant. + * Here only the timer part is used. + * + * The timer part is quite troublesome to program correctly. Lots + * of long delays must be there in order to secure that you actually get what + * you wrote. + * + * In other words, this timer is and should only used from cpuidle during + * special conditions when the surroundings are know in order to be able + * to remove the number of delays. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ktime.h> +#include <linux/delay.h> + +#include <asm/errno.h> + +#include <mach/hardware.h> + +#define RTC_IMSC 0x10 +#define RTC_MIS 0x18 +#define RTC_ICR 0x1C +#define RTC_TDR 0x20 +#define RTC_TLR1 0x24 +#define RTC_TCR 0x28 + +#define RTC_TLR2 0x2C +#define RTC_TPR1 0x3C + +#define RTC_TCR_RTTOS (1 << 0) +#define RTC_TCR_RTTEN (1 << 1) +#define RTC_TCR_RTTSS (1 << 2) + +#define RTC_IMSC_TIMSC (1 << 1) +#define RTC_ICR_TIC (1 << 1) +#define RTC_MIS_RTCTMIS (1 << 1) + +#define RTC_TCR_RTTPS_2 (1 << 4) +#define RTC_TCR_RTTPS_3 (2 << 4) +#define RTC_TCR_RTTPS_4 (3 << 4) +#define RTC_TCR_RTTPS_5 (4 << 4) +#define RTC_TCR_RTTPS_6 (5 << 4) +#define RTC_TCR_RTTPS_7 (6 << 4) +#define RTC_TCR_RTTPS_8 (7 << 4) + +#define WRITE_DELAY 130 /* 4 cycles plus margin */ + +/* + * Count down measure point. It just have to be high to differ + * from scheduled values. + */ +#define MEASURE_VAL 0xffffffff + +/* Just a value bigger than any reason able scheduled timeout. */ +#define MEASURE_VAL_LIMIT 0xf0000000 + + +#define TICKS_TO_NS(x) ((s64)x * 30512) +#define US_TO_TICKS(x) ((u32)((1000 * x) / 30512)) + +static void __iomem *rtc_base; +static bool measure_latency; + +#ifdef CONFIG_U8500_CPUIDLE_DEBUG + +/* + * The plan here is to be able to measure the ApSleep/ApDeepSleep exit latency + * by having a know timer pattern. + * The first entry in the pattern, LR1, is the value that the scheduler + * wants us to sleep. The second pattern in a high value, too large to be + * scheduled, so we can differ between a running scheduled value and a + * time measure value. + * When a RTT interrupt has occured, the block will automatically start + * to execute the measure value in LR2 and when the ARM is awake, it reads + * how far the RTT has decreased the value loaded from LR2 and from that + * calculate how long time it took to wake up. + */ +ktime_t u8500_rtc_exit_latency_get(void) +{ + u32 ticks; + + if (measure_latency) { + ticks = MEASURE_VAL - readl(rtc_base + RTC_TDR); + + /* + * Check if we are actually counting on a LR2 value. + * If not we have woken on another interrupt. + */ + if (ticks < MEASURE_VAL_LIMIT) { + /* convert 32 kHz ticks to ns */ + return ktime_set(0, TICKS_TO_NS(ticks)); + } + } + return ktime_set(0, 0); +} + +static void measure_latency_start(void) +{ + udelay(WRITE_DELAY); + /* + * Disable RTT and clean self-start due to we want to restart, + * not continue from current pattern. (See below) + */ + writel(0, rtc_base + RTC_TCR); + udelay(WRITE_DELAY); + + /* + * Program LR2 (load register two) to maximum value to ease + * identification of timer interrupt vs other. + */ + writel(MEASURE_VAL, rtc_base + RTC_TLR2); + /* + * Set Load Register execution pattern, bit clear + * means pick LR1, bit set means LR2 + * 0xfe, binary 11111110 means first do LR1 then do + * LR2 seven times + */ + writel(0xfe, rtc_base + RTC_TPR1); + + udelay(WRITE_DELAY); + + /* + * Enable self-start, plus a pattern of eight. + */ + writel(RTC_TCR_RTTSS | RTC_TCR_RTTPS_8, + rtc_base + RTC_TCR); + udelay(WRITE_DELAY); +} + +void ux500_rtcrtt_measure_latency(bool enable) +{ + if (enable) { + measure_latency_start(); + } else { + writel(RTC_TCR_RTTSS | RTC_TCR_RTTOS, rtc_base + RTC_TCR); + writel(RTC_ICR_TIC, rtc_base + RTC_ICR); + writel(RTC_IMSC_TIMSC, rtc_base + RTC_IMSC); + } + measure_latency = enable; +} +#else +static inline void measure_latency_start(void) { } +static inline void ux500_rtcrtt_measure_latency(bool enable) { } +#endif + +void ux500_rtcrtt_off(void) +{ + if (measure_latency) { + measure_latency_start(); + } else { + /* Clear eventual interrupts */ + if (readl(rtc_base + RTC_MIS) & RTC_MIS_RTCTMIS) + writel(RTC_ICR_TIC, rtc_base + RTC_ICR); + + /* Disable, self start and oneshot mode */ + writel(RTC_TCR_RTTSS | RTC_TCR_RTTOS, rtc_base + RTC_TCR); + } +} + +void ux500_rtcrtt_next(u32 time_us) +{ + writel(US_TO_TICKS(time_us), rtc_base + RTC_TLR1); +} + +static int __init ux500_rtcrtt_init(void) +{ + if (cpu_is_u8500()) { + rtc_base = __io_address(U8500_RTC_BASE); + } else if (cpu_is_u5500()) { + rtc_base = __io_address(U5500_RTC_BASE); + } else { + pr_err("timer-rtt: Unknown DB Asic!\n"); + return -EINVAL; + } + ux500_rtcrtt_measure_latency(false); + return 0; +} +subsys_initcall(ux500_rtcrtt_init); diff --git a/arch/arm/mach-ux500/pm/usecase_gov.c b/arch/arm/mach-ux500/pm/usecase_gov.c new file mode 100644 index 00000000000..8ca064da0a1 --- /dev/null +++ b/arch/arm/mach-ux500/pm/usecase_gov.c @@ -0,0 +1,943 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Alexandre Torgue <alexandre.torgue@stericsson.com> for ST-Ericsson + * Author: Vincent Guittot <vincent.guittot@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/io.h> +#include <linux/earlysuspend.h> +#include <linux/cpu.h> +#include <linux/sched.h> +#include <linux/tick.h> +#include <linux/workqueue.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/kernel_stat.h> +#include <linux/ktime.h> +#include <linux/cpufreq.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include "../../../../drivers/cpuidle/cpuidle-dbx500.h" + + +#define CPULOAD_MEAS_DELAY 3000 /* 3 secondes of delta */ + +/* debug */ +static unsigned long debug; + +#define hp_printk \ + if (debug) \ + printk \ + +enum ux500_uc { + UX500_UC_NORMAL = 0, + UX500_UC_AUTO, /* Add use case below this. */ + UX500_UC_VC, + UX500_UC_LPA, + UX500_UC_USER, /* Add use case above this. */ + UX500_UC_MAX, +}; + +/* cpu load monitor struct */ +#define LOAD_MONITOR 4 +struct hotplug_cpu_info { + cputime64_t prev_cpu_wall; + cputime64_t prev_cpu_idle; + cputime64_t prev_cpu_io; + unsigned int load[LOAD_MONITOR]; + unsigned int io[LOAD_MONITOR]; + unsigned int idx; +}; + +static DEFINE_PER_CPU(struct hotplug_cpu_info, hotplug_info); + +/* Auto trigger criteria */ +/* loadavg threshold */ +static unsigned long lower_threshold = 175; +static unsigned long upper_threshold = 450; +/* load balancing */ +static unsigned long max_unbalance = 210; +/* trend load */ +static unsigned long trend_unbalance = 40; +static unsigned long min_trend = 5; +/* instant load */ +static unsigned long max_instant = 85; + +/* Number of interrupts per second before exiting auto mode */ +static u32 exit_irq_per_s = 1000; +static u64 old_num_irqs; + +static DEFINE_MUTEX(usecase_mutex); +static bool user_config_updated; +static enum ux500_uc current_uc = UX500_UC_MAX; +static bool is_work_scheduled; +static bool is_early_suspend; +static bool uc_master_enable = true; + +static unsigned int cpuidle_deepest_state; + +struct usecase_config { + char *name; + unsigned long max_freq; + unsigned long min_freq; /* if no requirement set 0 */ + unsigned long cpuidle_multiplier; + bool second_cpu_online; + bool l2_prefetch_en; + bool enable; + unsigned int forced_state; /* Forced cpu idle state. */ + bool vc_override; /* QOS override for voice-call. */ +}; + +static struct usecase_config usecase_conf[UX500_UC_MAX] = { + [UX500_UC_NORMAL] = { + .name = "normal", + .max_freq = 1000000, + .min_freq = 200000, + .cpuidle_multiplier = 1024, + .second_cpu_online = true, + .l2_prefetch_en = true, + .enable = true, + .forced_state = 0, + .vc_override = false, + }, + [UX500_UC_AUTO] = { + .name = "auto", + .max_freq = 400000, + .min_freq = 200000, + .cpuidle_multiplier = 0, + .second_cpu_online = false, + .l2_prefetch_en = true, + .enable = false, + .forced_state = 0, + .vc_override = false, + }, + [UX500_UC_VC] = { + .name = "voice-call", + .max_freq = 400000, + .min_freq = 400000, + .cpuidle_multiplier = 0, + .second_cpu_online = false, + .l2_prefetch_en = false, + .enable = false, + .forced_state = 0, + .vc_override = true, + }, + [UX500_UC_LPA] = { + .name = "low-power-audio", + .max_freq = 400000, + .min_freq = 400000, + .cpuidle_multiplier = 0, + .second_cpu_online = false, + .l2_prefetch_en = false, + .enable = false, + .forced_state = 0, /* Updated dynamically */ + .vc_override = false, + }, +}; + +/* daemon */ +static struct delayed_work work_usecase; +static struct early_suspend usecase_early_suspend; + +/* calculate loadavg */ +#define LOAD_INT(x) ((x) >> FSHIFT) +#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100) + +extern int cpufreq_update_freq(int cpu, unsigned int min, unsigned int max); +extern int cpuidle_set_multiplier(unsigned int value); +extern int cpuidle_force_state(unsigned int state); + +static unsigned long determine_loadavg(void) +{ + unsigned long avg = 0; + unsigned long avnrun[3]; + + get_avenrun(avnrun, FIXED_1 / 200, 0); + avg += (LOAD_INT(avnrun[0]) * 100) + (LOAD_FRAC(avnrun[0]) % 100); + + return avg; +} + +static unsigned long determine_cpu_load(void) +{ + int i; + unsigned long total_load = 0; + + /* get cpu load of each cpu */ + for_each_online_cpu(i) { + unsigned int load, iowait; + unsigned int idle_time, iowait_time, wall_time; + cputime64_t cur_wall_time, cur_idle_time, cur_iowait_time; + struct hotplug_cpu_info *info; + + info = &per_cpu(hotplug_info, i); + + /* update both cur_idle_time and cur_wall_time */ + cur_idle_time = get_cpu_idle_time_us(i, &cur_wall_time); + cur_iowait_time = get_cpu_iowait_time_us(i, &cur_wall_time); + + /* how much wall time has passed since last iteration? */ + wall_time = (unsigned int) cputime64_sub(cur_wall_time, + info->prev_cpu_wall); + info->prev_cpu_wall = cur_wall_time; + + /* how much idle time has passed since last iteration? */ + idle_time = (unsigned int) cputime64_sub(cur_idle_time, + info->prev_cpu_idle); + info->prev_cpu_idle = cur_idle_time; + + /* how much io wait time has passed since last iteration? */ + iowait_time = (unsigned int) cputime64_sub(cur_iowait_time, + info->prev_cpu_io); + info->prev_cpu_io = cur_iowait_time; + + if (unlikely(!wall_time || wall_time < idle_time)) + continue; + + /* load is the percentage of time not spent in idle */ + load = 100 * (wall_time - idle_time) / wall_time; + info->load[info->idx] = load; + hp_printk("cpu %d load %u ", i, load); + + /* iowait is the percentage of time not spent in io wait */ + iowait = 100 * (iowait_time) / wall_time; + info->io[info->idx++] = load; + hp_printk("iowait %u\n", iowait); + + if (info->idx >= LOAD_MONITOR) + info->idx = 0; + + total_load += load; + } + + return total_load; +} + +static unsigned long determine_cpu_load_trend(void) +{ + int i, j, k; + unsigned long total_load = 0; + + /* Get cpu load of each cpu */ + for_each_online_cpu(i) { + unsigned int load = 0; + struct hotplug_cpu_info *info; + + info = &per_cpu(hotplug_info, i); + + for (k = 0, j = info->idx; k < LOAD_MONITOR; k++, j++) + load += info->load[j]; + + load /= LOAD_MONITOR; + + hp_printk("cpu %d load trend %u\n", i, load); + + total_load += load; + } + + return total_load; +} + +static unsigned long determine_cpu_balance_trend(void) +{ + int i, j, k; + unsigned long total_load = 0; + unsigned long min_load = (unsigned long) (-1); + + /* Get cpu load of each cpu */ + for_each_online_cpu(i) { + unsigned int load = 0; + struct hotplug_cpu_info *info; + + info = &per_cpu(hotplug_info, i); + + for (k = 0, j = info->idx; k < LOAD_MONITOR; k++, j++) + load += info->load[j]; + + load /= LOAD_MONITOR; + + if (min_load > load) + min_load = load; + total_load += load; + } + + if (min_load > min_trend) + total_load = (100 * total_load) / min_load; + else + total_load = 50 << num_online_cpus(); + + return total_load; +} + +static void init_cpu_load_trend(void) +{ + int i; + + for_each_possible_cpu(i) { + struct hotplug_cpu_info *info; + int j; + + info = &per_cpu(hotplug_info, i); + + info->prev_cpu_idle = get_cpu_idle_time_us(i, + &(info->prev_cpu_wall)); + info->prev_cpu_io = get_cpu_iowait_time_us(i, + &(info->prev_cpu_wall)); + + for (j = 0; j < LOAD_MONITOR; j++) { + info->load[j] = 100; + info->io[j] = 100; + } + info->idx = 0; + } +} + +static u32 get_num_interrupts_per_s(void) +{ + int cpu; + int i; + u64 num_irqs = 0; + ktime_t now; + static ktime_t last; + unsigned int delta; + u32 irqs = 0; + + now = ktime_get(); + + for_each_possible_cpu(cpu) { + for (i = 0; i < NR_IRQS; i++) + num_irqs += kstat_irqs_cpu(i, cpu); + } + pr_debug("%s: total num irqs: %lld, previous %lld\n", + __func__, num_irqs, old_num_irqs); + + if (old_num_irqs > 0) { + delta = (u32)ktime_to_ms(ktime_sub(now, last)) / 1000; + irqs = ((u32)(num_irqs - old_num_irqs)) / delta; + } + + old_num_irqs = num_irqs; + last = now; + + pr_debug("delta irqs per sec:%d\n", irqs); + + return irqs; +} + +static void set_cpu_config(enum ux500_uc new_uc) +{ + struct cpufreq_policy policy; + int err; + bool update = false; + u32 min_freq, max_freq; + + if (new_uc != current_uc) + update = true; + else if ((user_config_updated) && (new_uc == UX500_UC_USER)) + update = true; + + pr_debug("%s: new_usecase=%d, current_usecase=%d, update=%d\n", + __func__, new_uc, current_uc, update); + + if (!update) + goto exit; + + /* Cpu hotplug */ + if (!(usecase_conf[new_uc].second_cpu_online) && + (num_online_cpus() > 1)) + cpu_down(1); + else if ((usecase_conf[new_uc].second_cpu_online) && + (num_online_cpus() < 2)) + cpu_up(1); + + /* Cpu freq */ + err = cpufreq_get_policy(&policy, 0); + if (err) + pr_err("usecase-gov: get cpufreq policy failed\n"); + + /* If requirement is 0, use current policy value */ + min_freq = usecase_conf[new_uc].min_freq ? + usecase_conf[new_uc].min_freq : policy.min; + + max_freq = usecase_conf[new_uc].max_freq ? + usecase_conf[new_uc].max_freq : policy.max; + + /* + * cpufreq fw does not allow frequency change if + * "current min freq" > "new max freq" or + * "current max freq" < "new min freq". + * Thus the intermediate steps below. + */ + if (policy.min > max_freq) { + err = cpufreq_update_freq(0, min_freq, policy.max); + if (err) + pr_err("usecase-gov: update min cpufreq failed\n"); + } + if (policy.max < min_freq) { + err = cpufreq_update_freq(0, policy.min, max_freq); + if (err) + pr_err("usecase-gov: update max cpufreq failed\n"); + } + + err = cpufreq_update_freq(0, min_freq, max_freq); + if (err) + pr_err("usecase-gov: update min/max cpufreq failed\n"); + + /* Cpu idle */ + cpuidle_set_multiplier(usecase_conf[new_uc].cpuidle_multiplier); + + /* L2 prefetch */ + if (usecase_conf[new_uc].l2_prefetch_en) + outer_prefetch_enable(); + else + outer_prefetch_disable(); + + /* Force cpuidle state */ + cpuidle_force_state(usecase_conf[new_uc].forced_state); + + /* QOS override */ + prcmu_qos_voice_call_override(usecase_conf[new_uc].vc_override); + + current_uc = new_uc; + +exit: + /* Its ok to clear even if new_uc != UX500_UC_USER */ + user_config_updated = false; +} + +void usecase_update_governor_state(void) +{ + bool cancel_work = false; + + mutex_lock(&usecase_mutex); + + if (uc_master_enable && (usecase_conf[UX500_UC_AUTO].enable || + usecase_conf[UX500_UC_USER].enable)) { + /* + * Usecases are enabled. If we are in early suspend put + * governor to work. + */ + if (is_early_suspend && !is_work_scheduled) { + schedule_delayed_work_on(0, &work_usecase, + msecs_to_jiffies(CPULOAD_MEAS_DELAY)); + is_work_scheduled = true; + } else if (!is_early_suspend && is_work_scheduled) { + /* Exiting from early suspend. */ + cancel_work = true; + } + + } else if (is_work_scheduled) { + /* No usecase enabled or governor is not enabled. */ + cancel_work = true; + } + + if (cancel_work) { + cancel_delayed_work_sync(&work_usecase); + is_work_scheduled = false; + + /* Set the default settings before exiting. */ + set_cpu_config(UX500_UC_NORMAL); + } + + mutex_unlock(&usecase_mutex); + +} + +/* + * Start load measurment every 6 s in order detrmine if can unplug one CPU. + * In order to not corrupt measurment, the first load average is not done + * here call in early suspend. + */ +static void usecase_earlysuspend_callback(struct early_suspend *h) +{ + init_cpu_load_trend(); + + is_early_suspend = true; + + usecase_update_governor_state(); +} + +/* Stop measurement, call LCD early resume */ +static void usecase_lateresume_callback(struct early_suspend *h) +{ + is_early_suspend = false; + + usecase_update_governor_state(); +} + +static void delayed_usecase_work(struct work_struct *work) +{ + unsigned long avg, load, trend, balance; + bool inc_perf = false; + bool dec_perf = false; + u32 irqs_per_s; + + /* determine loadavg */ + avg = determine_loadavg(); + hp_printk("loadavg = %lu lower th %lu upper th %lu\n", + avg, lower_threshold, upper_threshold); + + /* determine instant load */ + load = determine_cpu_load(); + hp_printk("cpu instant load = %lu max %lu\n", load, max_instant); + + /* determine load trend */ + trend = determine_cpu_load_trend(); + hp_printk("cpu load trend = %lu min %lu unbal %lu\n", + trend, min_trend, trend_unbalance); + + /* determine load balancing */ + balance = determine_cpu_balance_trend(); + hp_printk("load balancing trend = %lu min %lu\n", + balance, max_unbalance); + + irqs_per_s = get_num_interrupts_per_s(); + + /* Dont let configuration change in the middle of our calculations. */ + mutex_lock(&usecase_mutex); + + /* detect "instant" load increase */ + if (load > max_instant || irqs_per_s > exit_irq_per_s) { + inc_perf = true; + } else if (!usecase_conf[UX500_UC_USER].enable && + usecase_conf[UX500_UC_AUTO].enable) { + /* detect high loadavg use case */ + if (avg > upper_threshold) + inc_perf = true; + /* detect idle use case */ + else if (trend < min_trend) + dec_perf = true; + /* detect unbalanced low cpu load use case */ + else if ((balance > max_unbalance) && (trend < trend_unbalance)) + dec_perf = true; + /* detect low loadavg use case */ + else if (avg < lower_threshold) + dec_perf = true; + /* All user use cases disabled, current load not triggering + * any change. + */ + else if (user_config_updated) + dec_perf = true; + } else { + dec_perf = true; + } + + /* + * set_cpu_config() will not update the config unless it has been + * changed. + */ + if (dec_perf) { + if (usecase_conf[UX500_UC_USER].enable) + set_cpu_config(UX500_UC_USER); + else if (usecase_conf[UX500_UC_AUTO].enable) + set_cpu_config(UX500_UC_AUTO); + } else if (inc_perf) { + set_cpu_config(UX500_UC_NORMAL); + } + + mutex_unlock(&usecase_mutex); + + /* reprogramm scheduled work */ + schedule_delayed_work_on(0, &work_usecase, + msecs_to_jiffies(CPULOAD_MEAS_DELAY)); + +} + +static struct dentry *usecase_dir; + +#ifdef CONFIG_DEBUG_FS +#define define_set(_name) \ +static ssize_t set_##_name(struct file *file, \ + const char __user *user_buf, \ + size_t count, loff_t *ppos) \ +{ \ + int err; \ + long unsigned i; \ + \ + err = kstrtoul_from_user(user_buf, count, 0, &i); \ + \ + if (err) \ + return err; \ + \ + _name = i; \ + hp_printk("New value : %lu\n", _name); \ + \ + return count; \ +} + +define_set(upper_threshold); +define_set(lower_threshold); +define_set(max_unbalance); +define_set(trend_unbalance); +define_set(min_trend); +define_set(max_instant); +define_set(debug); + +#define define_print(_name) \ +static ssize_t print_##_name(struct seq_file *s, void *p) \ +{ \ + return seq_printf(s, "%lu\n", _name); \ +} + +define_print(upper_threshold); +define_print(lower_threshold); +define_print(max_unbalance); +define_print(trend_unbalance); +define_print(min_trend); +define_print(max_instant); +define_print(debug); + +#define define_open(_name) \ +static ssize_t open_##_name(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, print_##_name, inode->i_private); \ +} + +define_open(upper_threshold); +define_open(lower_threshold); +define_open(max_unbalance); +define_open(trend_unbalance); +define_open(min_trend); +define_open(max_instant); +define_open(debug); + +#define define_dbg_file(_name) \ +static const struct file_operations fops_##_name = { \ + .open = open_##_name, \ + .write = set_##_name, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ + .owner = THIS_MODULE, \ +}; \ +static struct dentry *file_##_name; + +define_dbg_file(upper_threshold); +define_dbg_file(lower_threshold); +define_dbg_file(max_unbalance); +define_dbg_file(trend_unbalance); +define_dbg_file(min_trend); +define_dbg_file(max_instant); +define_dbg_file(debug); + +struct dbg_file { + struct dentry **file; + const struct file_operations *fops; + const char *name; +}; + +#define define_dbg_entry(_name) \ +{ \ + .file = &file_##_name, \ + .fops = &fops_##_name, \ + .name = #_name \ +} + +static struct dbg_file debug_entry[] = { + define_dbg_entry(upper_threshold), + define_dbg_entry(lower_threshold), + define_dbg_entry(max_unbalance), + define_dbg_entry(trend_unbalance), + define_dbg_entry(min_trend), + define_dbg_entry(max_instant), + define_dbg_entry(debug), +}; + +static int setup_debugfs(void) +{ + int i; + usecase_dir = debugfs_create_dir("usecase", NULL); + + if (IS_ERR_OR_NULL(usecase_dir)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(debug_entry); i++) { + if (IS_ERR_OR_NULL(debugfs_create_file(debug_entry[i].name, + S_IWUGO | S_IRUGO, + usecase_dir, + NULL, + debug_entry[i].fops))) + goto fail; + } + + if (IS_ERR_OR_NULL(debugfs_create_u32("exit_irq_per_s", + S_IWUGO | S_IRUGO, usecase_dir, + &exit_irq_per_s))) + goto fail; + return 0; +fail: + debugfs_remove_recursive(usecase_dir); + return -EINVAL; +} +#else +static int setup_debugfs(void) +{ + return 0; +} +#endif + +static void usecase_update_user_config(void) +{ + int i; + bool config_enable = false; + struct usecase_config *user_conf = &usecase_conf[UX500_UC_USER]; + + mutex_lock(&usecase_mutex); + + user_conf->max_freq = 0; + user_conf->min_freq = 0; + user_conf->cpuidle_multiplier = 0; + user_conf->second_cpu_online = false; + user_conf->l2_prefetch_en = false; + user_conf->forced_state = cpuidle_deepest_state; + user_conf->vc_override = true; /* A single false will clear it. */ + + /* Dont include Auto and Normal modes in this */ + for (i = (UX500_UC_AUTO + 1); i < UX500_UC_USER; i++) { + if (!usecase_conf[i].enable) + continue; + + config_enable = true; + + if (usecase_conf[i].max_freq > user_conf->max_freq) + user_conf->max_freq = usecase_conf[i].max_freq; + /* It's the highest min freq requirement that should be used */ + if (usecase_conf[i].min_freq > user_conf->min_freq) + user_conf->min_freq = usecase_conf[i].min_freq; + + if (usecase_conf[i].cpuidle_multiplier > + user_conf->cpuidle_multiplier) + user_conf->cpuidle_multiplier = + usecase_conf[i].cpuidle_multiplier; + + user_conf->second_cpu_online |= + usecase_conf[i].second_cpu_online; + + user_conf->l2_prefetch_en |= + usecase_conf[i].l2_prefetch_en; + + /* Take the shallowest state. */ + if (usecase_conf[i].forced_state < user_conf->forced_state) + user_conf->forced_state = usecase_conf[i].forced_state; + + /* Only override QOS if all enabled configurations are + * requesting it. + */ + if (!usecase_conf[i].vc_override) + user_conf->vc_override = false; + } + + user_conf->enable = config_enable; + user_config_updated = true; + + mutex_unlock(&usecase_mutex); +} + +struct usecase_devclass_attr { + struct sysdev_class_attribute class_attr; + u32 index; +}; + +/* One for each usecase except "user" + current + enable */ +#define UX500_NUM_SYSFS_NODES (UX500_UC_USER + 2) +#define UX500_CURRENT_NODE_INDEX (UX500_NUM_SYSFS_NODES - 1) +#define UX500_ENABLE_NODE_INDEX (UX500_NUM_SYSFS_NODES - 2) + +static struct usecase_devclass_attr usecase_dc_attr[UX500_NUM_SYSFS_NODES]; + +static struct attribute *dbs_attributes[UX500_NUM_SYSFS_NODES + 1] = {NULL}; + +static struct attribute_group dbs_attr_group = { + .attrs = dbs_attributes, + .name = "usecase", +}; + +static ssize_t show_current(struct sysdev_class *class, + struct sysdev_class_attribute *attr, char *buf) +{ + enum ux500_uc display_uc = (current_uc == UX500_UC_MAX) ? + UX500_UC_NORMAL : current_uc; + + return sprintf(buf, "max_freq: %ld\n" + "min_freq: %ld\n" + "cpuidle_multiplier: %ld\n" + "second_cpu_online: %s\n" + "l2_prefetch_en: %s\n" + "forced_state: %d\n" + "vc_override: %s\n", + usecase_conf[display_uc].max_freq, + usecase_conf[display_uc].min_freq, + usecase_conf[display_uc].cpuidle_multiplier, + usecase_conf[display_uc].second_cpu_online ? "true" : "false", + usecase_conf[display_uc].l2_prefetch_en ? "true" : "false", + usecase_conf[display_uc].forced_state, + usecase_conf[display_uc].vc_override ? "true" : "false"); +} + +static ssize_t show_enable(struct sysdev_class *class, + struct sysdev_class_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", uc_master_enable); +} + +static ssize_t store_enable(struct sysdev_class *class, + struct sysdev_class_attribute *attr, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; + + uc_master_enable = (bool) input; + + usecase_update_governor_state(); + + return count; +} + +static ssize_t show_dc_attr(struct sysdev_class *class, + struct sysdev_class_attribute *attr, char *buf) +{ + struct usecase_devclass_attr *uattr = + container_of(attr, struct usecase_devclass_attr, class_attr); + + return sprintf(buf, "%u\n", + usecase_conf[uattr->index].enable); +} + +static ssize_t store_dc_attr(struct sysdev_class *class, + struct sysdev_class_attribute *attr, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + + struct usecase_devclass_attr *uattr = + container_of(attr, struct usecase_devclass_attr, class_attr); + + ret = sscanf(buf, "%u", &input); + + /* Normal mode cant be changed. */ + if ((ret != 1) || (uattr->index == 0)) + return -EINVAL; + + usecase_conf[uattr->index].enable = (bool)input; + + usecase_update_user_config(); + + usecase_update_governor_state(); + + return count; +} + +static int usecase_sysfs_init(void) +{ + int err; + int i; + + /* Last two nodes are not based on usecase configurations */ + for (i = 0; i < (UX500_NUM_SYSFS_NODES - 2); i++) { + usecase_dc_attr[i].class_attr.attr.name = usecase_conf[i].name; + usecase_dc_attr[i].class_attr.attr.mode = 0644; + usecase_dc_attr[i].class_attr.show = show_dc_attr; + usecase_dc_attr[i].class_attr.store = store_dc_attr; + usecase_dc_attr[i].index = i; + + dbs_attributes[i] = &(usecase_dc_attr[i].class_attr.attr); + } + + /* sysfs current */ + usecase_dc_attr[UX500_CURRENT_NODE_INDEX].class_attr.attr.name = + "current"; + usecase_dc_attr[UX500_CURRENT_NODE_INDEX].class_attr.attr.mode = + 0644; + usecase_dc_attr[UX500_CURRENT_NODE_INDEX].class_attr.show = + show_current; + usecase_dc_attr[UX500_CURRENT_NODE_INDEX].class_attr.store = + NULL; + usecase_dc_attr[UX500_CURRENT_NODE_INDEX].index = + 0; + dbs_attributes[UX500_CURRENT_NODE_INDEX] = + &(usecase_dc_attr[UX500_CURRENT_NODE_INDEX].class_attr.attr); + + /* sysfs enable */ + usecase_dc_attr[UX500_ENABLE_NODE_INDEX].class_attr.attr.name = + "enable"; + usecase_dc_attr[UX500_ENABLE_NODE_INDEX].class_attr.attr.mode = + 0644; + usecase_dc_attr[UX500_ENABLE_NODE_INDEX].class_attr.show = + show_enable; + usecase_dc_attr[UX500_ENABLE_NODE_INDEX].class_attr.store = + store_enable; + usecase_dc_attr[UX500_ENABLE_NODE_INDEX].index = + 0; + dbs_attributes[UX500_ENABLE_NODE_INDEX] = + &(usecase_dc_attr[UX500_ENABLE_NODE_INDEX].class_attr.attr); + + err = sysfs_create_group(&(cpu_sysdev_class.kset.kobj), + &dbs_attr_group); + if (err) + pr_err("usecase-gov: sysfs_create_group" + " failed with error = %d\n", err); + + return err; +} + +static void usecase_cpuidle_init(void) +{ + int max_states; + int i; + struct cstate *state = ux500_ci_get_cstates(&max_states); + + for (i = 0; i < max_states; i++) + if ((state[i].APE == APE_OFF) && (state[i].ARM == ARM_RET)) + break; + + usecase_conf[UX500_UC_LPA].forced_state = i; + + cpuidle_deepest_state = max_states - 1; +} + +/* initialize devices */ +static int __init init_usecase_devices(void) +{ + int err; + + pr_info("Use-case governor initialized\n"); + + /* add early_suspend callback */ + usecase_early_suspend.level = 200; + usecase_early_suspend.suspend = usecase_earlysuspend_callback; + usecase_early_suspend.resume = usecase_lateresume_callback; + register_early_suspend(&usecase_early_suspend); + + /* register delayed queuework */ + INIT_DELAYED_WORK_DEFERRABLE(&work_usecase, + delayed_usecase_work); + + init_cpu_load_trend(); + + err = setup_debugfs(); + if (err) + goto error; + err = usecase_sysfs_init(); + if (err) + goto error2; + + usecase_cpuidle_init(); + + return 0; +error2: + debugfs_remove_recursive(usecase_dir); +error: + unregister_early_suspend(&usecase_early_suspend); + return err; +} + +device_initcall(init_usecase_devices); diff --git a/arch/arm/mach-ux500/prcmu-debug.c b/arch/arm/mach-ux500/prcmu-debug.c new file mode 100644 index 00000000000..61d19c0e1e0 --- /dev/null +++ b/arch/arm/mach-ux500/prcmu-debug.c @@ -0,0 +1,555 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Martin Persson for ST-Ericsson + * Etienne Carriere <etienne.carriere@stericsson.com> for ST-Ericsson + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include <mach/hardware.h> + +#define MAX_STATES 5 +#define MAX_NAMELEN 16 + +struct state_history { + ktime_t start; + u32 state; + u32 counter[MAX_STATES]; + u8 opps[MAX_STATES]; + int max_states; + int req; + bool reqs[MAX_STATES]; + ktime_t time[MAX_STATES]; + int state_names[MAX_STATES]; + char prefix[MAX_NAMELEN]; + spinlock_t lock; +}; + +static struct state_history ape_sh = { + .prefix = "APE", + .req = PRCMU_QOS_APE_OPP, + .opps = {APE_50_OPP, APE_100_OPP}, + .state_names = {50, 100}, + .max_states = 2, +}; + +static struct state_history ddr_sh = { + .prefix = "DDR", + .req = PRCMU_QOS_DDR_OPP, + .opps = {DDR_25_OPP, DDR_50_OPP, DDR_100_OPP}, + .state_names = {25, 50, 100}, + .max_states = 3, +}; + +static struct state_history arm_sh = { + .prefix = "ARM", + .req = PRCMU_QOS_ARM_OPP, + .opps = {ARM_EXTCLK, ARM_50_OPP, ARM_100_OPP, ARM_MAX_OPP}, + .state_names = {25, 50, 100, 125}, + .max_states = 4, +}; + +static int ape_voltage_count; + +static void log_set(struct state_history *sh, u8 opp) +{ + ktime_t now; + ktime_t dtime; + unsigned long flags; + int state; + + now = ktime_get(); + spin_lock_irqsave(&sh->lock, flags); + + for (state = 0 ; sh->opps[state] != opp; state++) + ; + BUG_ON(state >= sh->max_states); + + dtime = ktime_sub(now, sh->start); + sh->time[sh->state] = ktime_add(sh->time[sh->state], dtime); + sh->start = now; + sh->counter[sh->state]++; + sh->state = state; + + spin_unlock_irqrestore(&sh->lock, flags); +} + +void prcmu_debug_ape_opp_log(u8 opp) +{ + log_set(&ape_sh, opp); +} + +void prcmu_debug_ddr_opp_log(u8 opp) +{ + log_set(&ddr_sh, opp); +} + +void prcmu_debug_arm_opp_log(u8 opp) +{ + log_set(&arm_sh, opp); +} + +static void log_reset(struct state_history *sh) +{ + unsigned long flags; + int i; + + pr_info("reset\n"); + + spin_lock_irqsave(&sh->lock, flags); + for (i = 0; i < sh->max_states; i++) { + sh->counter[i] = 0; + sh->time[i] = ktime_set(0, 0); + } + + sh->start = ktime_get(); + spin_unlock_irqrestore(&sh->lock, flags); + +} + +static ssize_t ape_stats_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + log_reset(&ape_sh); + return count; +} + +static ssize_t ddr_stats_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + log_reset(&ddr_sh); + return count; +} + +static ssize_t arm_stats_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + log_reset(&arm_sh); + return count; +} + +static int log_print(struct seq_file *s, struct state_history *sh) +{ + int i; + unsigned long flags; + ktime_t total; + ktime_t dtime; + s64 t_ms; + s64 perc; + s64 total_ms; + + spin_lock_irqsave(&sh->lock, flags); + + dtime = ktime_sub(ktime_get(), sh->start); + + total = dtime; + + for (i = 0; i < sh->max_states; i++) + total = ktime_add(total, sh->time[i]); + total_ms = ktime_to_ms(total); + + for (i = 0; i < sh->max_states; i++) { + ktime_t t = sh->time[i]; + if (sh->state == i) + t = ktime_add(t, dtime); + + t_ms = ktime_to_ms(t); + perc = 100 * t_ms; + do_div(perc, total_ms); + + seq_printf(s, "%s OPP %d: # %u in %lld ms %d%%\n", + sh->prefix, sh->state_names[i], + sh->counter[i] + (int)(sh->state == i), + t_ms, (u32)perc); + + } + spin_unlock_irqrestore(&sh->lock, flags); + return 0; +} + +static int ape_stats_print(struct seq_file *s, void *p) +{ + log_print(s, &ape_sh); + return 0; +} + +static int ddr_stats_print(struct seq_file *s, void *p) +{ + log_print(s, &ddr_sh); + return 0; +} + +static int arm_stats_print(struct seq_file *s, void *p) +{ + log_print(s, &arm_sh); + return 0; +} + +static int opp_read(struct seq_file *s, void *p) +{ + int opp; + + struct state_history *sh = (struct state_history *)s->private; + + switch (sh->req) { + case PRCMU_QOS_DDR_OPP: + opp = prcmu_get_ddr_opp(); + seq_printf(s, "%s (%d)\n", + (opp == DDR_100_OPP) ? "100%" : + (opp == DDR_50_OPP) ? "50%" : + (opp == DDR_25_OPP) ? "25%" : + "unknown", opp); + break; + case PRCMU_QOS_APE_OPP: + opp = prcmu_get_ape_opp(); + seq_printf(s, "%s (%d)\n", + (opp == APE_100_OPP) ? "100%" : + (opp == APE_50_OPP) ? "50%" : + "unknown", opp); + break; + case PRCMU_QOS_ARM_OPP: + opp = prcmu_get_arm_opp(); + seq_printf(s, "%s (%d)\n", + (opp == ARM_MAX_OPP) ? "max" : + (opp == ARM_MAX_FREQ100OPP) ? "max-freq100" : + (opp == ARM_100_OPP) ? "100%" : + (opp == ARM_50_OPP) ? "50%" : + (opp == ARM_EXTCLK) ? "25% (extclk)" : + "unknown", opp); + break; + default: + break; + } + return 0; + +} + +static ssize_t opp_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + long unsigned i; + int err; + struct state_history *sh = (struct state_history *) + ((struct seq_file *)file->private_data)->private; + + err = kstrtoul_from_user(user_buf, count, 0, &i); + + if (err) + return err; + + prcmu_qos_force_opp(sh->req, i); + + pr_info("prcmu debug: forced OPP for %s to %d\n", sh->prefix, (int)i); + + return count; +} + +static int cpufreq_delay_read(struct seq_file *s, void *p) +{ + return seq_printf(s, "%lu\n", prcmu_qos_get_cpufreq_opp_delay()); +} + +static int ape_voltage_read(struct seq_file *s, void *p) +{ + return seq_printf(s, "This reference count only includes " + "requests via debugfs.\nCount: %d\n", + ape_voltage_count); +} + +static ssize_t ape_voltage_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + long unsigned i; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &i); + + if (err) + return err; + + switch (i) { + case 0: + if (ape_voltage_count == 0) + pr_info("prcmu debug: reference count is already 0\n"); + else { + err = prcmu_request_ape_opp_100_voltage(false); + if (err) + pr_err("prcmu debug: drop request failed\n"); + else + ape_voltage_count--; + } + break; + case 1: + err = prcmu_request_ape_opp_100_voltage(true); + if (err) + pr_err("prcmu debug: request failed\n"); + else + ape_voltage_count++; + break; + default: + pr_info("prcmu debug: value not equal to 0 or 1\n"); + } + return count; +} + +static ssize_t cpufreq_delay_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int err; + long unsigned i; + + err = kstrtoul_from_user(user_buf, count, 0, &i); + + if (err) + return err; + + prcmu_qos_set_cpufreq_opp_delay(i); + + pr_info("prcmu debug: changed delay between cpufreq change and QoS " + "requirement to %lu.\n", i); + + return count; +} + +/* These are only for u8500 */ +#define PRCM_AVS_BASE 0x2FC +#define AVS_VBB_RET 0x0 +#define AVS_VBB_MAX_OPP 0x1 +#define AVS_VBB_100_OPP 0x2 +#define AVS_VBB_50_OPP 0x3 +#define AVS_VARM_MAX_OPP 0x4 +#define AVS_VARM_100_OPP 0x5 +#define AVS_VARM_50_OPP 0x6 +#define AVS_VARM_RET 0x7 +#define AVS_VAPE_100_OPP 0x8 +#define AVS_VAPE_50_OPP 0x9 +#define AVS_VMOD_100_OPP 0xA +#define AVS_VMOD_50_OPP 0xB +#define AVS_VSAFE 0xC +#define AVS_SIZE 14 + +static int avs_read(struct seq_file *s, void *p) +{ + + u8 avs[AVS_SIZE]; + void __iomem *tcdm_base; + + if (cpu_is_u8500()) { + tcdm_base = __io_address(U8500_PRCMU_TCDM_BASE); + + memcpy_fromio(avs, tcdm_base + PRCM_AVS_BASE, AVS_SIZE); + + seq_printf(s, "VBB_RET : 0x%2x\n", avs[AVS_VBB_RET]); + seq_printf(s, "VBB_MAX_OPP : 0x%2x\n", avs[AVS_VBB_MAX_OPP]); + seq_printf(s, "VBB_100_OPP : 0x%2x\n", avs[AVS_VBB_100_OPP]); + seq_printf(s, "VBB_50_OPP : 0x%2x\n", avs[AVS_VBB_50_OPP]); + seq_printf(s, "VARM_MAX_OPP : 0x%2x\n", avs[AVS_VARM_MAX_OPP]); + seq_printf(s, "VARM_100_OPP : 0x%2x\n", avs[AVS_VARM_100_OPP]); + seq_printf(s, "VARM_50_OPP : 0x%2x\n", avs[AVS_VARM_50_OPP]); + seq_printf(s, "VARM_RET : 0x%2x\n", avs[AVS_VARM_RET]); + seq_printf(s, "VAPE_100_OPP : 0x%2x\n", avs[AVS_VAPE_100_OPP]); + seq_printf(s, "VAPE_50_OPP : 0x%2x\n", avs[AVS_VAPE_50_OPP]); + seq_printf(s, "VMOD_100_OPP : 0x%2x\n", avs[AVS_VMOD_100_OPP]); + seq_printf(s, "VMOD_50_OPP : 0x%2x\n", avs[AVS_VMOD_50_OPP]); + seq_printf(s, "VSAFE : 0x%2x\n", avs[AVS_VSAFE]); + } else { + seq_printf(s, "Only u8500 supported.\n"); + } + + return 0; +} + +static int opp_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, opp_read, inode->i_private); +} + +static int ape_stats_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, ape_stats_print, inode->i_private); +} + +static int ddr_stats_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, ddr_stats_print, inode->i_private); +} + +static int arm_stats_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, arm_stats_print, inode->i_private); +} + +static int cpufreq_delay_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, cpufreq_delay_read, inode->i_private); +} + +static int ape_voltage_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, ape_voltage_read, inode->i_private); +} + +static int avs_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, avs_read, inode->i_private); +} + +static const struct file_operations opp_fops = { + .open = opp_open_file, + .write = opp_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ape_stats_fops = { + .open = ape_stats_open_file, + .write = ape_stats_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ddr_stats_fops = { + .open = ddr_stats_open_file, + .write = ddr_stats_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations arm_stats_fops = { + .open = arm_stats_open_file, + .write = arm_stats_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations cpufreq_delay_fops = { + .open = cpufreq_delay_open_file, + .write = cpufreq_delay_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ape_voltage_fops = { + .open = ape_voltage_open_file, + .write = ape_voltage_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations avs_fops = { + .open = avs_open_file, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int setup_debugfs(void) +{ + struct dentry *dir; + struct dentry *file; + + dir = debugfs_create_dir("prcmu", NULL); + if (IS_ERR_OR_NULL(dir)) + goto fail; + + file = debugfs_create_file("ape_stats", (S_IRUGO | S_IWUGO), + dir, NULL, &ape_stats_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("ddr_stats", (S_IRUGO | S_IWUGO), + dir, NULL, &ddr_stats_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("arm_stats", (S_IRUGO | S_IWUGO), + dir, NULL, &arm_stats_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("ape_opp", (S_IRUGO), + dir, (void *)&ape_sh, + &opp_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("ddr_opp", (S_IRUGO), + dir, (void *)&ddr_sh, + &opp_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("arm_opp", (S_IRUGO), + dir, (void *)&arm_sh, + &opp_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("opp_cpufreq_delay", (S_IRUGO), + dir, NULL, &cpufreq_delay_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("ape_voltage", (S_IRUGO), + dir, NULL, &ape_voltage_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + file = debugfs_create_file("avs", + (S_IRUGO), + dir, NULL, &avs_fops); + if (IS_ERR_OR_NULL(file)) + goto fail; + + return 0; +fail: + if (!IS_ERR_OR_NULL(dir)) + debugfs_remove_recursive(dir); + + pr_err("prcmu debug: debugfs entry failed\n"); + return -ENOMEM; +} + +static __init int prcmu_debug_init(void) +{ + spin_lock_init(&ape_sh.lock); + spin_lock_init(&ddr_sh.lock); + spin_lock_init(&arm_sh.lock); + ape_sh.start = ktime_get(); + ddr_sh.start = ktime_get(); + arm_sh.start = ktime_get(); + setup_debugfs(); + return 0; +} +late_initcall(prcmu_debug_init); diff --git a/arch/arm/mach-ux500/product.c b/arch/arm/mach-ux500/product.c new file mode 100644 index 00000000000..5e8eba9b16a --- /dev/null +++ b/arch/arm/mach-ux500/product.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Jens Wiklander <jens.wiklander@stericsson.com> + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + * Author: Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com> + * + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/tee.h> +#include <linux/module.h> +#include <mach/hardware.h> + +#define STATIC_TEE_TA_START_LOW 0xBC765EDE +#define STATIC_TEE_TA_START_MID 0x6724 +#define STATIC_TEE_TA_START_HIGH 0x11DF +#define STATIC_TEE_TA_START_CLOCKSEQ \ + {0x8E, 0x12, 0xEC, 0xDB, 0xDF, 0xD7, 0x20, 0x85} + +#define U5500_PRCMU_DBG_PWRCTRL (U5500_PRCMU_BASE + 0x4AC) +#define PRCMU_DBG_PWRCTRL_A9DBGCLKEN (1 << 4) + +static struct tee_product_config product_config; + +bool ux500_jtag_enabled(void) +{ +#ifdef CONFIG_UX500_DEBUG_NO_LAUTERBACH + return false; +#else + if (cpu_is_u5500()) + return readl_relaxed(__io_address(U5500_PRCMU_DBG_PWRCTRL)) + & PRCMU_DBG_PWRCTRL_A9DBGCLKEN; + + if (cpu_is_u8500()) + return (product_config.rt_flags & TEE_RT_FLAGS_JTAG_ENABLED) == + TEE_RT_FLAGS_JTAG_ENABLED; + + return true; +#endif +} + +static int __init product_detect(void) +{ + int err; + int origin_err; + struct tee_operation operation = { { { 0 } } }; + struct tee_context context; + struct tee_session session; + + /* Selects trustzone application needed for the job. */ + struct tee_uuid static_uuid = { + STATIC_TEE_TA_START_LOW, + STATIC_TEE_TA_START_MID, + STATIC_TEE_TA_START_HIGH, + STATIC_TEE_TA_START_CLOCKSEQ, + }; + + if (cpu_is_u5500()) + return -ENODEV; + + err = teec_initialize_context(NULL, &context); + if (err) { + pr_err("ux500-product: unable to initialize tee context," + " err = %d\n", err); + err = -EINVAL; + goto error0; + } + + err = teec_open_session(&context, &session, &static_uuid, + TEEC_LOGIN_PUBLIC, NULL, NULL, &origin_err); + if (err) { + pr_err("ux500-product: unable to open tee session," + " tee error = %d, origin error = %d\n", + err, origin_err); + err = -EINVAL; + goto error1; + } + + operation.shm[0].buffer = &product_config; + operation.shm[0].size = sizeof(product_config); + operation.shm[0].flags = TEEC_MEM_OUTPUT; + operation.flags = TEEC_MEMREF_0_USED; + + err = teec_invoke_command(&session, + TEE_STA_GET_PRODUCT_CONFIG, + &operation, &origin_err); + if (err) { + pr_err("ux500-product: fetching product settings failed, err=%d", + err); + err = -EINVAL; + goto error1; + } + + pr_info("ux500-product: JTAG is %s\n", + ux500_jtag_enabled()? "enabled" : "disabled"); +error1: + (void) teec_finalize_context(&context); +error0: + return err; +} +device_initcall(product_detect); + diff --git a/arch/arm/mach-ux500/product.h b/arch/arm/mach-ux500/product.h new file mode 100644 index 00000000000..502eff4df14 --- /dev/null +++ b/arch/arm/mach-ux500/product.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Jens Wiklander <jens.wiklander@stericsson.com> + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + * + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#ifndef UX500_PRODUCT_H +#define UX500_PRODUCT_H + +#ifdef CONFIG_TEE_UX500 + +bool ux500_jtag_enabled(void); + +#else + +static inline bool ux500_jtag_enabled(void) +{ + return true; +} + +#endif +#endif diff --git a/arch/arm/mach-ux500/reboot_reasons.c b/arch/arm/mach-ux500/reboot_reasons.c new file mode 100644 index 00000000000..365b0c02e16 --- /dev/null +++ b/arch/arm/mach-ux500/reboot_reasons.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Rickard Evertsson <rickard.evertsson@stericsson.com> + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL) version 2 + * + * Use this file to customize your reboot / sw reset reasons. Add, remove or + * modify reasons in reboot_reasons[]. + */ + +#include <linux/kernel.h> +#include <linux/string.h> +#include <mach/reboot_reasons.h> + +struct reboot_reason reboot_reasons[] = { + {"crash", SW_RESET_CRASH}, + {"factory-reset", SW_RESET_FACTORY_RESET}, + {"recovery", SW_RESET_RECOVERY}, + {"charging", SW_RESET_CHARGING}, + {"coldstart", SW_RESET_COLDSTART}, + {"none", SW_RESET_NO_ARGUMENT}, /* Normal Boot */ +}; + +unsigned int reboot_reasons_size = ARRAY_SIZE(reboot_reasons); + +/* + * The reboot reason string can be 255 characters long and the memory + * in which we save the sw reset reason is 2 bytes. Therefore we need to + * convert the string into a 16 bit pattern. + * + * See file reboot_reasons.h for conversion. + */ +u16 reboot_reason_code(const char *cmd) +{ + int i; + + if (cmd == NULL) { + if (oops_in_progress) { + /* if we're in an oops assume it's a crash */ + return SW_RESET_CRASH; + } else { + /* normal reboot w/o argument */ + return SW_RESET_NO_ARGUMENT; + } + } + + /* Search through reboot reason list */ + for (i = 0; i < reboot_reasons_size; i++) { + if (!strcmp(reboot_reasons[i].reason, cmd)) + return reboot_reasons[i].code; + } + + /* No valid reboot reason found */ + return SW_RESET_NO_ARGUMENT; +} + +/* + * The saved sw reset reason is a 2 byte code that is translated into + * a reboot reason string which is up to 255 characters long by this + * function. + * + * See file reboot_reasons.h for conversion. + */ +const char *reboot_reason_string(u16 code) +{ + int i; + + /* Search through reboot reason list */ + for (i = 0; i < reboot_reasons_size; i++) { + if (reboot_reasons[i].code == code) + return reboot_reasons[i].reason; + } + + /* No valid reboot reason code found */ + return "unknown"; +} diff --git a/arch/arm/mach-ux500/regulator-u5500.h b/arch/arm/mach-ux500/regulator-u5500.h new file mode 100644 index 00000000000..cf3eeed9366 --- /dev/null +++ b/arch/arm/mach-ux500/regulator-u5500.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + */ + +#ifndef __REGULATOR_U5500_H +#define __REGULATOR_U5500_H + +enum u5500_regulator_id { + U5500_REGULATOR_VAPE, + U5500_REGULATOR_SWITCH_SGA, + U5500_REGULATOR_SWITCH_HVA, + U5500_REGULATOR_SWITCH_SIA, + U5500_REGULATOR_SWITCH_DISP, + U5500_REGULATOR_SWITCH_ESRAM12, + U5500_NUM_REGULATORS +}; + +#endif diff --git a/arch/arm/mach-ux500/sensors1p.c b/arch/arm/mach-ux500/sensors1p.c new file mode 100644 index 00000000000..e7f4642b1d9 --- /dev/null +++ b/arch/arm/mach-ux500/sensors1p.c @@ -0,0 +1,298 @@ + +/* + * Copyright (C) 2009-2010 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * Simple userspace interface for + * Proximity Sensor Osram SFH 7741 and HAL switch Samsung HED54XXU11 + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + * + * This driver is only there for making Android happy. It is not ment + * for mainline. + */ + + +#include <linux/platform_device.h> +#include <linux/sysfs.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> + +#include <mach/sensors1p.h> + +struct sensor { + struct regulator *regulator; + int pin; + int startup_time; + int active; + u64 when_enabled; +}; + +struct sensors1p { + struct sensor hal; + struct sensor proximity; +}; + +static int sensors1p_power_write(struct device *dev, + struct sensor *s, const char *buf) +{ + int val; + + if (sscanf(buf, "%d", &val) != 1) + return -EINVAL; + + if (val != 0 && val != 1) + return -EINVAL; + + if (val != s->active) { + if (val) { + regulator_enable(s->regulator); + s->when_enabled = get_jiffies_64() + + msecs_to_jiffies(s->startup_time); + } else + regulator_disable(s->regulator); + } + s->active = val; + + return strnlen(buf, PAGE_SIZE); + +} + +static ssize_t sensors1p_sysfs_hal_active_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + + + struct sensors1p *s = platform_get_drvdata(container_of(dev, + struct platform_device, + dev)); + return sensors1p_power_write(dev, &s->hal, buf); + +} + +static ssize_t sensors1p_sysfs_proximity_active_set(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + + + struct sensors1p *s = platform_get_drvdata(container_of(dev, + struct platform_device, + dev)); + return sensors1p_power_write(dev, &s->proximity, buf); + +} + +static ssize_t sensors1p_sysfs_hal_active_get(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensors1p *s = platform_get_drvdata(container_of(dev, + struct platform_device, + dev)); + return sprintf(buf, "%d", s->hal.active); +} + +static ssize_t sensors1p_sysfs_proximity_active_get(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensors1p *s = platform_get_drvdata(container_of(dev, + struct platform_device, + dev)); + return sprintf(buf, "%d", s->proximity.active); +} + +static int sensors1p_read(struct device *dev, struct sensor *s, char *buf) +{ + int ret; + + if (!s->active) + return -EINVAL; + + /* Only wait if read() is called before the sensor is up and running + * Since jiffies wraps, always sleep maximum time. + */ + if (time_before64(get_jiffies_64(), s->when_enabled)) + mdelay(s->startup_time); + + /* For some odd reason, setting direction in the probe function fails */ + ret = gpio_direction_input(s->pin); + + if (ret) + dev_err(dev, "Failed to set GPIO pin %d to input.\n", s->pin); + else + ret = gpio_get_value(s->pin); + + return sprintf(buf, "%d", ret); +} + +static ssize_t sensors1p_sysfs_hal_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensors1p *s = platform_get_drvdata(container_of(dev, + struct platform_device, + dev)); + return sensors1p_read(dev, &s->hal, buf); +} + +static ssize_t sensors1p_sysfs_proximity_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct sensors1p *s = platform_get_drvdata(container_of(dev, + struct platform_device, + dev)); + return sensors1p_read(dev, &s->proximity, buf); +} + +static DEVICE_ATTR(proximity_activate, 0666, + sensors1p_sysfs_proximity_active_get, + sensors1p_sysfs_proximity_active_set); +static DEVICE_ATTR(hal_activate, 0666, + sensors1p_sysfs_hal_active_get, + sensors1p_sysfs_hal_active_set); +static DEVICE_ATTR(proximity, 0444, sensors1p_sysfs_proximity_show, NULL); +static DEVICE_ATTR(hal, 0444, sensors1p_sysfs_hal_show, NULL); + +static struct attribute *sensors1p_attrs[] = { + &dev_attr_proximity_activate.attr, + &dev_attr_hal_activate.attr, + &dev_attr_proximity.attr, + &dev_attr_hal.attr, + NULL, +}; + +static struct attribute_group sensors1p_attr_group = { + .name = NULL, + .attrs = sensors1p_attrs, +}; + +static int __init sensors1p_probe(struct platform_device *pdev) +{ + int err = -EINVAL; + struct sensors1p_config *c; + struct sensors1p *s = NULL; + + if (!pdev) + goto out; + + c = pdev->dev.platform_data; + + if (c == NULL) { + dev_err(&pdev->dev, "Error: Missconfigured.\n"); + goto out; + } + + s = kzalloc(sizeof(struct sensors1p), GFP_KERNEL); + + if (s == NULL) { + dev_err(&pdev->dev, + "Could not allocate struct memory!\n"); + err = -ENOMEM; + goto out; + } + + s->hal.pin = c->hal.pin; + err = gpio_request(c->hal.pin, "hal sensor"); + if (err < 0) { + dev_err(&pdev->dev, "gpio_request failed with err: %d", err); + goto err_hal_gpio; + } + + s->proximity.pin = c->proximity.pin; + err = gpio_request(c->proximity.pin, "proximity sensor"); + if (err < 0) { + dev_err(&pdev->dev, "gpio_request failed with err: %d", err); + goto err_proximity_gpio; + } + + s->hal.startup_time = c->hal.startup_time; + s->proximity.startup_time = c->proximity.startup_time; + + + s->hal.regulator = regulator_get(&pdev->dev, c->hal.regulator); + + if (IS_ERR(s->hal.regulator)) { + dev_err(&pdev->dev, "regulator_get(\"%s\") failed.\n", + c->hal.regulator); + err = PTR_ERR(s->hal.regulator); + goto err_hal_reg; + } + s->proximity.regulator = regulator_get(&pdev->dev, + c->proximity.regulator); + + if (IS_ERR(s->proximity.regulator)) { + dev_err(&pdev->dev, "regulator_get(\"%s\") failed.\n", + c->proximity.regulator); + err = PTR_ERR(s->proximity.regulator); + goto err_proximity_reg; + } + + err = sysfs_create_group(&pdev->dev.kobj, &sensors1p_attr_group); + + if (err) { + dev_err(&pdev->dev, "Failed to create sysfs entries.\n"); + goto err_sysfs; + } + + platform_set_drvdata(pdev, s); + + return 0; + +err_sysfs: + regulator_put(s->proximity.regulator); +err_proximity_reg: + regulator_put(s->hal.regulator); +err_hal_reg: + gpio_free(s->proximity.pin); +err_proximity_gpio: + gpio_free(s->hal.pin); +err_hal_gpio: + kfree(s); +out: + return err; +} + +static int __exit sensors1p_remove(struct platform_device *pdev) +{ + struct sensors1p *s = platform_get_drvdata(pdev); + + sysfs_remove_group(&pdev->dev.kobj, &sensors1p_attr_group); + gpio_free(s->hal.pin); + gpio_free(s->proximity.pin); + regulator_put(s->hal.regulator); + regulator_put(s->proximity.regulator); + kfree(s); + return 0; +} + +static struct platform_driver sensors1p_driver = { + .remove = __exit_p(sensors1p_remove), + .driver = { + .name = "sensors1p", + .owner = THIS_MODULE, + }, +}; + +static int __init sensors1p_init(void) +{ + return platform_driver_probe(&sensors1p_driver, sensors1p_probe); +} + +static void __exit sensors1p_exit(void) +{ + platform_driver_unregister(&sensors1p_driver); +} + +late_initcall(sensors1p_init); +module_exit(sensors1p_exit); + +MODULE_AUTHOR("Jonas Aaberg <jonas.aberg@stericsson.com>"); +MODULE_DESCRIPTION("One pin gpio sensors driver (Proximity+HAL)"); +MODULE_LICENSE("GPLv2"); diff --git a/arch/arm/mach-ux500/timer.c b/arch/arm/mach-ux500/timer.c new file mode 100644 index 00000000000..8a8f747bd81 --- /dev/null +++ b/arch/arm/mach-ux500/timer.c @@ -0,0 +1,84 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Author: Mattias Wallin <mattias.wallin@stericsson.com> for ST-Ericsson + */ +#include <linux/io.h> +#include <linux/clksrc-dbx500-prcmu.h> + +#include <asm/localtimer.h> + +#include <plat/mtu.h> + +#include <mach/setup.h> +#include <mach/hardware.h> +#include <mach/context.h> + +#ifdef CONFIG_UX500_CONTEXT +static int mtu_context_notifier_call(struct notifier_block *this, + unsigned long event, void *data) +{ + if (event == CONTEXT_APE_RESTORE) + nmdk_clksrc_reset(); + return NOTIFY_OK; +} + +static struct notifier_block mtu_context_notifier = { + .notifier_call = mtu_context_notifier_call, +}; +#endif + +static void ux500_timer_reset(void) +{ + nmdk_clkevt_reset(); +} + +static void __init ux500_timer_init(void) +{ + if (cpu_is_u5500()) { +#ifdef CONFIG_LOCAL_TIMERS + twd_base = __io_address(U5500_TWD_BASE); +#endif + mtu_base = __io_address(U5500_MTU0_BASE); + clksrc_dbx500_timer_base = __io_address(U5500_PRCMU_TIMER_3_BASE); + } else if (cpu_is_u8500()) { +#ifdef CONFIG_LOCAL_TIMERS + twd_base = __io_address(U8500_TWD_BASE); +#endif + mtu_base = __io_address(U8500_MTU0_BASE); + clksrc_dbx500_timer_base = __io_address(U8500_PRCMU_TIMER_4_BASE); + } else { + ux500_unknown_soc(); + } + + /* + * Here we register the timerblocks active in the system. + * Localtimers (twd) is started when both cpu is up and running. + * MTU register a clocksource, clockevent and sched_clock. + * Since the MTU is located in the VAPE power domain + * it will be cleared in sleep which makes it unsuitable. + * We however need it as a timer tick (clockevent) + * during boot to calibrate delay until twd is started. + * RTC-RTT have problems as timer tick during boot since it is + * depending on delay which is not yet calibrated. RTC-RTT is in the + * always-on powerdomain and is used as clockevent instead of twd when + * sleeping. + * The PRCMU timer 4(3 for DB5500) register a clocksource and + * sched_clock with higher rating then MTU since is always-on. + * + */ + + nmdk_timer_init(); + clksrc_dbx500_prcmu_init(); + +#ifdef CONFIG_UX500_CONTEXT + WARN_ON(context_ape_notifier_register(&mtu_context_notifier)); +#endif + +} + +struct sys_timer ux500_timer = { + .init = ux500_timer_init, + .resume = ux500_timer_reset, +}; diff --git a/arch/arm/mach-ux500/uart-db8500.c b/arch/arm/mach-ux500/uart-db8500.c new file mode 100644 index 00000000000..fad9b9a13df --- /dev/null +++ b/arch/arm/mach-ux500/uart-db8500.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Arun R Murthy <arun.murthy@stericsson.com>, + * Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/amba/serial.h> +#include <mach/setup.h> +#include <mach/hardware.h> +#include <mach/context.h> + +#ifdef CONFIG_UX500_CONTEXT + +static struct { + struct clk *uart_clk; + void __iomem *base; + /* dr */ + /* rsr_err */ + u32 dma_wm; + u32 timeout; + /* fr */ + u32 lcrh_rx; + u32 ilpr; + u32 ibrd; + u32 fbrd; + u32 lcrh_tx; + u32 cr; + u32 ifls; + u32 imsc; + /* ris */ + /* mis */ + /* icr */ + u32 dmacr; + u32 xfcr; + u32 xon1; + u32 xon2; + u32 xoff1; + u32 xoff2; + /* itcr */ + /* itip */ + /* itop */ + /* tdr */ + u32 abcr; + /* absr */ + /* abfmt */ + /* abdr */ + /* abdfr */ + /* abmr */ + u32 abimsc; + /* abris */ + /* abmis */ + /* abicr */ + /* id_product_h_xy */ + /* id_provider */ + /* periphid0 */ + /* periphid1 */ + /* periphid2 */ + /* periphid3 */ + /* pcellid0 */ + /* pcellid1 */ + /* pcellid2 */ + /* pcellid3 */ +} context_uart; + +static void save_uart(void) +{ + void __iomem *membase; + + membase = context_uart.base; + + clk_enable(context_uart.uart_clk); + + context_uart.dma_wm = readl_relaxed(membase + ST_UART011_DMAWM); + context_uart.timeout = readl_relaxed(membase + ST_UART011_TIMEOUT); + context_uart.lcrh_rx = readl_relaxed(membase + ST_UART011_LCRH_RX); + context_uart.ilpr = readl_relaxed(membase + UART01x_ILPR); + context_uart.ibrd = readl_relaxed(membase + UART011_IBRD); + context_uart.fbrd = readl_relaxed(membase + UART011_FBRD); + context_uart.lcrh_tx = readl_relaxed(membase + ST_UART011_LCRH_TX); + context_uart.cr = readl_relaxed(membase + UART011_CR); + context_uart.ifls = readl_relaxed(membase + UART011_IFLS); + context_uart.imsc = readl_relaxed(membase + UART011_IMSC); + context_uart.dmacr = readl_relaxed(membase + UART011_DMACR); + context_uart.xfcr = readl_relaxed(membase + ST_UART011_XFCR); + context_uart.xon1 = readl_relaxed(membase + ST_UART011_XON1); + context_uart.xon2 = readl_relaxed(membase + ST_UART011_XON2); + context_uart.xoff1 = readl_relaxed(membase + ST_UART011_XOFF1); + context_uart.xoff2 = readl_relaxed(membase + ST_UART011_XOFF2); + context_uart.abcr = readl_relaxed(membase + ST_UART011_ABCR); + context_uart.abimsc = readl_relaxed(membase + ST_UART011_ABIMSC); + + clk_disable(context_uart.uart_clk); +} + +static void restore_uart(void) +{ + int cnt; + int retries = 100; + unsigned int cr; + void __iomem *membase; + u16 dummy; + bool show_warn = false; + + membase = context_uart.base; + clk_enable(context_uart.uart_clk); + + writew_relaxed(context_uart.ifls, membase + UART011_IFLS); + cr = UART01x_CR_UARTEN | UART011_CR_TXE | UART011_CR_LBE; + + writew_relaxed(cr, membase + UART011_CR); + writew_relaxed(0, membase + UART011_FBRD); + writew_relaxed(1, membase + UART011_IBRD); + writew_relaxed(0, membase + ST_UART011_LCRH_RX); + if (context_uart.lcrh_tx != ST_UART011_LCRH_RX) { + int i; + /* + * Wait 10 PCLKs before writing LCRH_TX register, + * to get this delay write read only register 10 times + */ + for (i = 0; i < 10; ++i) + dummy = readw(membase + ST_UART011_LCRH_RX); + writew_relaxed(0, membase + ST_UART011_LCRH_TX); + } + writew(0, membase + UART01x_DR); + do { + if (!(readw(membase + UART01x_FR) & UART01x_FR_BUSY)) + break; + cpu_relax(); + } while (retries-- > 0); + if (retries < 0) + /* + * We can't print out a warning here since the uart is + * not fully restored. Do it later. + */ + show_warn = true; + + writel_relaxed(context_uart.dma_wm, membase + ST_UART011_DMAWM); + writel_relaxed(context_uart.timeout, membase + ST_UART011_TIMEOUT); + writel_relaxed(context_uart.lcrh_rx, membase + ST_UART011_LCRH_RX); + writel_relaxed(context_uart.ilpr, membase + UART01x_ILPR); + writel_relaxed(context_uart.ibrd, membase + UART011_IBRD); + writel_relaxed(context_uart.fbrd, membase + UART011_FBRD); + /* + * Wait 10 PCLKs before writing LCRH_TX register, + * to get this delay write read only register 10-3 + * times, as already there are 3 writes after + * ST_UART011_LCRH_RX + */ + for (cnt = 0; cnt < 7; cnt++) + dummy = readw(membase + ST_UART011_LCRH_RX); + + writel_relaxed(context_uart.lcrh_tx, membase + ST_UART011_LCRH_TX); + writel_relaxed(context_uart.ifls, membase + UART011_IFLS); + writel_relaxed(context_uart.dmacr, membase + UART011_DMACR); + writel_relaxed(context_uart.xfcr, membase + ST_UART011_XFCR); + writel_relaxed(context_uart.xon1, membase + ST_UART011_XON1); + writel_relaxed(context_uart.xon2, membase + ST_UART011_XON2); + writel_relaxed(context_uart.xoff1, membase + ST_UART011_XOFF1); + writel_relaxed(context_uart.xoff2, membase + ST_UART011_XOFF2); + writel_relaxed(context_uart.abcr, membase + ST_UART011_ABCR); + writel_relaxed(context_uart.abimsc, membase + ST_UART011_ABIMSC); + writel_relaxed(context_uart.cr, membase + UART011_CR); + writel(context_uart.imsc, membase + UART011_IMSC); + + clk_disable(context_uart.uart_clk); + + if (show_warn) + pr_warning("%s:uart tx busy\n", __func__); +} + +static int uart_context_notifier_call(struct notifier_block *this, + unsigned long event, void *data) +{ + switch (event) { + case CONTEXT_APE_SAVE: + save_uart(); + break; + + case CONTEXT_APE_RESTORE: + restore_uart(); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block uart_context_notifier = { + .notifier_call = uart_context_notifier_call, +}; + +#define __UART_BASE(soc, x) soc##_UART##x##_BASE +#define UART_BASE(soc, x) __UART_BASE(soc, x) + +static int __init uart_context_notifier_init(void) +{ + unsigned long base; + static const char clkname[] __initconst + = "uart" __stringify(CONFIG_UX500_DEBUG_UART); + + if (cpu_is_u8500()) + base = UART_BASE(U8500, CONFIG_UX500_DEBUG_UART); + else if (cpu_is_u5500()) + base = UART_BASE(U5500, CONFIG_UX500_DEBUG_UART); + else + ux500_unknown_soc(); + + context_uart.base = ioremap(base, SZ_4K); + context_uart.uart_clk = clk_get_sys(clkname, NULL); + + if (IS_ERR(context_uart.uart_clk)) { + pr_err("%s:unable to get clk-uart%d\n", __func__, + CONFIG_UX500_DEBUG_UART); + return -EINVAL; + } + + return WARN_ON(context_ape_notifier_register(&uart_context_notifier)); +} +arch_initcall(uart_context_notifier_init); +#endif diff --git a/arch/arm/mm/cache-fa.S b/arch/arm/mm/cache-fa.S index 07201637109..226f8736152 100644 --- a/arch/arm/mm/cache-fa.S +++ b/arch/arm/mm/cache-fa.S @@ -240,6 +240,24 @@ ENTRY(fa_dma_unmap_area) mov pc, lr ENDPROC(fa_dma_unmap_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(fa_clean_dcache_all) + mov pc, lr +ENDPROC(fa_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(fa_flush_dcache_all) + mov pc, lr +ENDPROC(fa_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see <asm/cacheflush.h> and proc-macros.S) diff --git a/arch/arm/mm/cache-v3.S b/arch/arm/mm/cache-v3.S index c2301f22610..ab5bf508a2a 100644 --- a/arch/arm/mm/cache-v3.S +++ b/arch/arm/mm/cache-v3.S @@ -127,6 +127,24 @@ ENTRY(v3_dma_map_area) ENDPROC(v3_dma_unmap_area) ENDPROC(v3_dma_map_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v3_clean_dcache_all) + mov pc, lr +ENDPROC(v3_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v3_flush_dcache_all) + mov pc, lr +ENDPROC(v3_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see <asm/cacheflush.h> and proc-macros.S) diff --git a/arch/arm/mm/cache-v4.S b/arch/arm/mm/cache-v4.S index fd9bb7addc8..9d3a055127e 100644 --- a/arch/arm/mm/cache-v4.S +++ b/arch/arm/mm/cache-v4.S @@ -139,6 +139,24 @@ ENTRY(v4_dma_map_area) ENDPROC(v4_dma_unmap_area) ENDPROC(v4_dma_map_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v4_clean_dcache_all) + mov pc, lr +ENDPROC(v4_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v4_flush_dcache_all) + mov pc, lr +ENDPROC(v4_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see <asm/cacheflush.h> and proc-macros.S) diff --git a/arch/arm/mm/cache-v4wb.S b/arch/arm/mm/cache-v4wb.S index 4f2c14151cc..54d3cda4a89 100644 --- a/arch/arm/mm/cache-v4wb.S +++ b/arch/arm/mm/cache-v4wb.S @@ -251,6 +251,24 @@ ENTRY(v4wb_dma_unmap_area) mov pc, lr ENDPROC(v4wb_dma_unmap_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v4wb_clean_dcache_all) + mov pc, lr +ENDPROC(v4wb_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v4wb_flush_dcache_all) + mov pc, lr +ENDPROC(v4wb_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see <asm/cacheflush.h> and proc-macros.S) diff --git a/arch/arm/mm/cache-v4wt.S b/arch/arm/mm/cache-v4wt.S index 4d7b467631c..40f7dba11f5 100644 --- a/arch/arm/mm/cache-v4wt.S +++ b/arch/arm/mm/cache-v4wt.S @@ -195,6 +195,24 @@ ENTRY(v4wt_dma_map_area) ENDPROC(v4wt_dma_unmap_area) ENDPROC(v4wt_dma_map_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v4wt_clean_dcache_all) + mov pc, lr +ENDPROC(v4wt_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v4wt_flush_dcache_all) + mov pc, lr +ENDPROC(v4wt_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see <asm/cacheflush.h> and proc-macros.S) diff --git a/arch/arm/mm/cache-v6.S b/arch/arm/mm/cache-v6.S index 74c2e5a33a4..b88dd4ab038 100644 --- a/arch/arm/mm/cache-v6.S +++ b/arch/arm/mm/cache-v6.S @@ -328,6 +328,24 @@ ENTRY(v6_dma_unmap_area) mov pc, lr ENDPROC(v6_dma_unmap_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v6_clean_dcache_all) + mov pc, lr +ENDPROC(v6_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v6_flush_dcache_all) + mov pc, lr +ENDPROC(v6_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see <asm/cacheflush.h> and proc-macros.S) diff --git a/arch/arm/mm/cache-v7.S b/arch/arm/mm/cache-v7.S index 07c4bc8ea0a..3c77277d8cb 100644 --- a/arch/arm/mm/cache-v7.S +++ b/arch/arm/mm/cache-v7.S @@ -33,7 +33,7 @@ ENTRY(v7_flush_icache_all) ENDPROC(v7_flush_icache_all) /* - * v7_flush_dcache_all() + * __v7_flush_dcache_all() * * Flush the whole D-cache. * @@ -41,7 +41,7 @@ ENDPROC(v7_flush_icache_all) * * - mm - mm_struct describing address space */ -ENTRY(v7_flush_dcache_all) +ENTRY(__v7_flush_dcache_all) dmb @ ensure ordering with previous memory accesses mrc p15, 1, r0, c0, c0, 1 @ read clidr ands r3, r0, #0x7000000 @ extract loc from clidr @@ -88,9 +88,93 @@ finished: dsb isb mov pc, lr +ENDPROC(__v7_flush_dcache_all) + +/* + * __v7_clean_dcache_all() + * + * Clean the whole D-cache. + * + * Corrupted registers: r0-r7, r9-r11 (r6 only in Thumb mode) + */ +ENTRY(__v7_clean_dcache_all) + dmb @ ensure ordering with previous memory accesses + mrc p15, 1, r0, c0, c0, 1 @ read clidr + ands r3, r0, #0x7000000 @ extract loc from clidr + mov r3, r3, lsr #23 @ left align loc bit field + beq finished1 @ if loc is 0, then no need to clean + mov r10, #0 @ start clean at cache level 0 +loop21: + add r2, r10, r10, lsr #1 @ work out 3x current cache level + mov r1, r0, lsr r2 @ extract cache type bits from clidr + and r1, r1, #7 @ mask of the bits for current cache only + cmp r1, #2 @ see what cache we have at this level + blt skip1 @ skip if no cache, or just i-cache + mcr p15, 2, r10, c0, c0, 0 @ select current cache level in cssr + isb @ isb to sych the new cssr&csidr + mrc p15, 1, r1, c0, c0, 0 @ read the new csidr + and r2, r1, #7 @ extract the length of the cache lines + add r2, r2, #4 @ add 4 (line length offset) + ldr r4, =0x3ff + ands r4, r4, r1, lsr #3 @ find maximum number on the way size + clz r5, r4 @ find bit position of way size increment + ldr r7, =0x7fff + ands r7, r7, r1, lsr #13 @ extract max number of the index size +loop22: + mov r9, r4 @ create working copy of max way size +loop23: + ARM( orr r11, r10, r9, lsl r5 ) @ factor way and cache number into r11 + THUMB( lsl r6, r9, r5 ) + THUMB( orr r11, r10, r6 ) @ factor way and cache number into r11 + ARM( orr r11, r11, r7, lsl r2 ) @ factor index number into r11 + THUMB( lsl r6, r7, r2 ) + THUMB( orr r11, r11, r6 ) @ factor index number into r11 + mcr p15, 0, r11, c7, c10, 2 @ clean by set/way + subs r9, r9, #1 @ decrement the way + bge loop23 + subs r7, r7, #1 @ decrement the index + bge loop22 +skip1: + add r10, r10, #2 @ increment cache number + cmp r3, r10 + bgt loop21 +finished1: + mov r10, #0 @ swith back to cache level 0 + mcr p15, 2, r10, c0, c0, 0 @ select current cache level in cssr + dsb + isb + mov pc, lr +ENDPROC(__v7_clean_dcache_all) + +/* + * v7_flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v7_flush_dcache_all) + ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) + bl __v7_flush_dcache_all + ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) + mov pc, lr ENDPROC(v7_flush_dcache_all) /* + * v7_clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v7_clean_dcache_all) + ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) + bl __v7_clean_dcache_all + ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) + mov pc, lr +ENDPROC(v7_clean_dcache_all) + +/* * v7_flush_cache_all() * * Flush the entire cache system. @@ -102,14 +186,12 @@ ENDPROC(v7_flush_dcache_all) * */ ENTRY(v7_flush_kern_cache_all) - ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) - THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) + stmfd sp!, {lr} bl v7_flush_dcache_all mov r0, #0 ALT_SMP(mcr p15, 0, r0, c7, c1, 0) @ invalidate I-cache inner shareable ALT_UP(mcr p15, 0, r0, c7, c5, 0) @ I+BTB cache invalidate - ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) - THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) + ldmfd sp!, {lr} mov pc, lr ENDPROC(v7_flush_kern_cache_all) diff --git a/arch/arm/mm/mmu.c b/arch/arm/mm/mmu.c index 594d677b92c..df6ced286ea 100644 --- a/arch/arm/mm/mmu.c +++ b/arch/arm/mm/mmu.c @@ -273,6 +273,20 @@ static struct mem_type mem_types[] = { .prot_l1 = PMD_TYPE_TABLE, .domain = DOMAIN_KERNEL, }, + /* NOTE : this is only a temporary hack!!! + * The U8500 ED/V1.0 cuts require such a + * memory type for deep sleep resume. + * This is expected to be solved in cut v2.0 + * and we clean this up then. for more details + * look @ the commit message please + */ + [MT_BACKUP_RAM] = { + .prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED | + L_PTE_SHARED, + .prot_l1 = PMD_TYPE_TABLE, + .prot_sect = PROT_SECT_DEVICE | PMD_SECT_S, + .domain = DOMAIN_IO, + }, }; const struct mem_type *get_mem_type(unsigned int type) diff --git a/arch/arm/mm/proc-macros.S b/arch/arm/mm/proc-macros.S index 307a4def8d3..411610d4973 100644 --- a/arch/arm/mm/proc-macros.S +++ b/arch/arm/mm/proc-macros.S @@ -303,6 +303,8 @@ ENTRY(\name\()_cache_fns) .long \name\()_coherent_kern_range .long \name\()_coherent_user_range .long \name\()_flush_kern_dcache_area + .long \name\()_clean_dcache_all + .long \name\()_flush_dcache_all .long \name\()_dma_map_area .long \name\()_dma_unmap_area .long \name\()_dma_flush_range diff --git a/arch/arm/mm/proc-v7.S b/arch/arm/mm/proc-v7.S index 9049c0764db..2cd0273bc65 100644 --- a/arch/arm/mm/proc-v7.S +++ b/arch/arm/mm/proc-v7.S @@ -304,7 +304,7 @@ __v7_ca15mp_setup: __v7_setup: adr r12, __v7_setup_stack @ the local stack stmia r12, {r0-r5, r7, r9, r11, lr} - bl v7_flush_dcache_all + bl __v7_flush_dcache_all ldmia r12, {r0-r5, r7, r9, r11, lr} mrc p15, 0, r0, c0, c0, 0 @ read main ID register diff --git a/arch/arm/plat-nomadik/Kconfig b/arch/arm/plat-nomadik/Kconfig index ce659015535..bca4914b4b9 100644 --- a/arch/arm/plat-nomadik/Kconfig +++ b/arch/arm/plat-nomadik/Kconfig @@ -15,10 +15,16 @@ if PLAT_NOMADIK config HAS_MTU bool - select HAVE_SCHED_CLOCK help Support for Multi Timer Unit. MTU provides access to multiple interrupt generating programmable 32-bit free running decrementing counters. +config NOMADIK_MTU_SCHED_CLOCK + bool + depends on HAS_MTU + select HAVE_SCHED_CLOCK + help + Use the Multi Timer Unit as the sched_clock. + endif diff --git a/arch/arm/plat-nomadik/include/plat/mtu.h b/arch/arm/plat-nomadik/include/plat/mtu.h index 65704a3d424..5c97b3ccf76 100644 --- a/arch/arm/plat-nomadik/include/plat/mtu.h +++ b/arch/arm/plat-nomadik/include/plat/mtu.h @@ -1,54 +1,13 @@ #ifndef __PLAT_MTU_H #define __PLAT_MTU_H -/* - * Guaranteed runtime conversion range in seconds for - * the clocksource and clockevent. - */ -#define MTU_MIN_RANGE 4 - /* should be set by the platform code */ extern void __iomem *mtu_base; -/* - * The MTU device hosts four different counters, with 4 set of - * registers. These are register names. - */ - -#define MTU_IMSC 0x00 /* Interrupt mask set/clear */ -#define MTU_RIS 0x04 /* Raw interrupt status */ -#define MTU_MIS 0x08 /* Masked interrupt status */ -#define MTU_ICR 0x0C /* Interrupt clear register */ - -/* per-timer registers take 0..3 as argument */ -#define MTU_LR(x) (0x10 + 0x10 * (x) + 0x00) /* Load value */ -#define MTU_VAL(x) (0x10 + 0x10 * (x) + 0x04) /* Current value */ -#define MTU_CR(x) (0x10 + 0x10 * (x) + 0x08) /* Control reg */ -#define MTU_BGLR(x) (0x10 + 0x10 * (x) + 0x0c) /* At next overflow */ - -/* bits for the control register */ -#define MTU_CRn_ENA 0x80 -#define MTU_CRn_PERIODIC 0x40 /* if 0 = free-running */ -#define MTU_CRn_PRESCALE_MASK 0x0c -#define MTU_CRn_PRESCALE_1 0x00 -#define MTU_CRn_PRESCALE_16 0x04 -#define MTU_CRn_PRESCALE_256 0x08 -#define MTU_CRn_32BITS 0x02 -#define MTU_CRn_ONESHOT 0x01 /* if 0 = wraps reloading from BGLR*/ - -/* Other registers are usual amba/primecell registers, currently not used */ -#define MTU_ITCR 0xff0 -#define MTU_ITOP 0xff4 - -#define MTU_PERIPH_ID0 0xfe0 -#define MTU_PERIPH_ID1 0xfe4 -#define MTU_PERIPH_ID2 0xfe8 -#define MTU_PERIPH_ID3 0xfeC +void nmdk_clkevt_reset(void); +void nmdk_clksrc_reset(void); -#define MTU_PCELL0 0xff0 -#define MTU_PCELL1 0xff4 -#define MTU_PCELL2 0xff8 -#define MTU_PCELL3 0xffC +struct clock_event_device *nmdk_clkevt_get(void); #endif /* __PLAT_MTU_H */ diff --git a/arch/arm/plat-nomadik/include/plat/pincfg.h b/arch/arm/plat-nomadik/include/plat/pincfg.h index 05a3936ae6d..c015133a7ad 100644 --- a/arch/arm/plat-nomadik/include/plat/pincfg.h +++ b/arch/arm/plat-nomadik/include/plat/pincfg.h @@ -24,6 +24,7 @@ * bit 16..18 - SLPM pull up/down state * bit 19..20 - SLPM direction * bit 21..22 - SLPM Value (if output) + * bit 23..25 - PDIS value (if input) * * to facilitate the definition, the following macros are provided * @@ -37,7 +38,6 @@ * SLPM value = same as normal * * PIN_CFG - default config with alternate function - * PIN_CFG_PULL - default config with alternate function and pull up/down */ typedef unsigned long pin_cfg_t; @@ -68,6 +68,10 @@ typedef unsigned long pin_cfg_t; /* These two replace the above in DB8500v2+ */ #define PIN_SLPM_WAKEUP_ENABLE (NMK_GPIO_SLPM_WAKEUP_ENABLE << PIN_SLPM_SHIFT) #define PIN_SLPM_WAKEUP_DISABLE (NMK_GPIO_SLPM_WAKEUP_DISABLE << PIN_SLPM_SHIFT) +#define PIN_SLPM_USE_MUX_SETTINGS_IN_SLEEP PIN_SLPM_WAKEUP_DISABLE + +#define PIN_SLPM_GPIO PIN_SLPM_WAKEUP_ENABLE /* In SLPM, pin is a gpio */ +#define PIN_SLPM_ALTFUNC PIN_SLPM_WAKEUP_DISABLE /* In SLPM, pin is altfunc */ #define PIN_DIR_SHIFT 14 #define PIN_DIR_MASK (0x1 << PIN_DIR_SHIFT) @@ -106,6 +110,20 @@ typedef unsigned long pin_cfg_t; #define PIN_SLPM_VAL_LOW ((1 + 0) << PIN_SLPM_VAL_SHIFT) #define PIN_SLPM_VAL_HIGH ((1 + 1) << PIN_SLPM_VAL_SHIFT) +#define PIN_SLPM_PDIS_SHIFT 23 +#define PIN_SLPM_PDIS_MASK (0x3 << PIN_SLPM_PDIS_SHIFT) +#define PIN_SLPM_PDIS(x) \ + (((x) & PIN_SLPM_PDIS_MASK) >> PIN_SLPM_PDIS_SHIFT) +#define PIN_SLPM_PDIS_NO_CHANGE (0 << PIN_SLPM_PDIS_SHIFT) +#define PIN_SLPM_PDIS_DISABLED (1 << PIN_SLPM_PDIS_SHIFT) +#define PIN_SLPM_PDIS_ENABLED (2 << PIN_SLPM_PDIS_SHIFT) + +#define PIN_LOWEMI_SHIFT 25 +#define PIN_LOWEMI_MASK (0x1 << PIN_LOWEMI_SHIFT) +#define PIN_LOWEMI(x) (((x) & PIN_LOWEMI_MASK) >> PIN_LOWEMI_SHIFT) +#define PIN_LOWEMI_DISABLED (0 << PIN_LOWEMI_SHIFT) +#define PIN_LOWEMI_ENABLED (1 << PIN_LOWEMI_SHIFT) + /* Shortcuts. Use these instead of separate DIR, PULL, and VAL. */ #define PIN_INPUT_PULLDOWN (PIN_DIR_INPUT | PIN_PULL_DOWN) #define PIN_INPUT_PULLUP (PIN_DIR_INPUT | PIN_PULL_UP) @@ -133,10 +151,6 @@ typedef unsigned long pin_cfg_t; (PIN_CFG_DEFAULT |\ (PIN_NUM(num) | PIN_##alt | PIN_OUTPUT_##val)) -#define PIN_CFG_PULL(num, alt, pull) \ - ((PIN_CFG_DEFAULT & ~PIN_PULL_MASK) |\ - (PIN_NUM(num) | PIN_##alt | PIN_PULL_##pull)) - extern int nmk_config_pin(pin_cfg_t cfg, bool sleep); extern int nmk_config_pins(pin_cfg_t *cfgs, int num); extern int nmk_config_pins_sleep(pin_cfg_t *cfgs, int num); diff --git a/arch/arm/plat-nomadik/include/plat/ste_dma40.h b/arch/arm/plat-nomadik/include/plat/ste_dma40.h index 685c78716d9..de86ef9b401 100644 --- a/arch/arm/plat-nomadik/include/plat/ste_dma40.h +++ b/arch/arm/plat-nomadik/include/plat/ste_dma40.h @@ -13,14 +13,8 @@ #include <linux/scatterlist.h> #include <linux/workqueue.h> #include <linux/interrupt.h> - -/* - * Maxium size for a single dma descriptor - * Size is limited to 16 bits. - * Size is in the units of addr-widths (1,2,4,8 bytes) - * Larger transfers will be split up to multiple linked desc - */ -#define STEDMA40_MAX_SEG_SIZE 0xFFFF +#include <linux/dmaengine.h> +#include <linux/version.h> /* dev types for memcpy */ #define STEDMA40_DEV_DST_MEMORY (-1) @@ -71,9 +65,9 @@ enum stedma40_flow_ctrl { }; enum stedma40_periph_data_width { - STEDMA40_BYTE_WIDTH = STEDMA40_ESIZE_8_BIT, - STEDMA40_HALFWORD_WIDTH = STEDMA40_ESIZE_16_BIT, - STEDMA40_WORD_WIDTH = STEDMA40_ESIZE_32_BIT, + STEDMA40_BYTE_WIDTH = STEDMA40_ESIZE_8_BIT, + STEDMA40_HALFWORD_WIDTH = STEDMA40_ESIZE_16_BIT, + STEDMA40_WORD_WIDTH = STEDMA40_ESIZE_32_BIT, STEDMA40_DOUBLEWORD_WIDTH = STEDMA40_ESIZE_64_BIT }; @@ -84,22 +78,22 @@ enum stedma40_xfer_dir { STEDMA40_PERIPH_TO_PERIPH }; - /** - * struct stedma40_chan_cfg - dst/src channel configuration + * struct stedma40_chan_cfg - dst/src channel configurration * - * @big_endian: true if the src/dst should be read as big endian + * @big_endian: true if src/dst should be read as big_endian * @data_width: Data width of the src/dst hardware - * @p_size: Burst size + * @psize: Burst size * @flow_ctrl: Flow control on/off. */ struct stedma40_half_channel_info { - bool big_endian; + bool big_endian; enum stedma40_periph_data_width data_width; - int psize; - enum stedma40_flow_ctrl flow_ctrl; + int psize; + enum stedma40_flow_ctrl flow_ctrl; }; + /** * struct stedma40_chan_cfg - Structure to be filled by client drivers. * @@ -113,7 +107,8 @@ struct stedma40_half_channel_info { * @dst_dev_type: Dst device type * @src_info: Parameters for dst half channel * @dst_info: Parameters for dst half channel - * + * @use_fixed_channel: if true, use the physical channel specified by phy_channel + * @phy_channel: physical channel to use, only if use_fixed_channel is true * * This structure has to be filled by the client drivers. * It is recommended to do all dma configurations for clients in the machine. @@ -121,14 +116,19 @@ struct stedma40_half_channel_info { */ struct stedma40_chan_cfg { enum stedma40_xfer_dir dir; + bool high_priority; bool realtime; enum stedma40_mode mode; enum stedma40_mode_opt mode_opt; + int src_dev_type; int dst_dev_type; struct stedma40_half_channel_info src_info; struct stedma40_half_channel_info dst_info; + + bool use_fixed_channel; + int phy_channel; }; /** @@ -142,6 +142,7 @@ struct stedma40_chan_cfg { * @memcpy_conf_phy: default configuration of physical channel memcpy * @memcpy_conf_log: default configuration of logical channel memcpy * @disabled_channels: A vector, ending with -1, that marks physical channels + * @use_esram_lcla: flag for mapping the lcla into esram region * that are for different reasons not available for the driver. */ struct stedma40_platform_data { @@ -153,9 +154,105 @@ struct stedma40_platform_data { struct stedma40_chan_cfg *memcpy_conf_phy; struct stedma40_chan_cfg *memcpy_conf_log; int disabled_channels[STEDMA40_MAX_PHYS]; + bool use_esram_lcla; }; -#ifdef CONFIG_STE_DMA40 +struct d40_desc; + +/** + * struct stedma40_cyclic_desc - Cyclic DMA descriptor + * @d40d: DMA driver internal descriptor + * @period_callback: callback to be called after every link/period if + * the DMA_PREP_INTERRUPT flag is used when preparing + * the transaction + * @period_callback_param: handle passed to the period_callback + * + * A pointer to a structure of this type is returned from the + * stedma40_cyclic_prep_sg() function. The period_callback and + * period_callback_param members can be set by the client. + */ +struct stedma40_cyclic_desc { + struct d40_desc *d40d; + dma_async_tx_callback period_callback; + void *period_callback_param; +}; + +int stedma40_set_dev_addr(struct dma_chan *chan, + dma_addr_t src_dev_addr, + dma_addr_t dst_dev_addr); + +/* + * stedma40_get_src_addr - get current source address + * @chan: the DMA channel + * + * Returns the physical address of the current source element to be read by the + * DMA. + */ +dma_addr_t stedma40_get_src_addr(struct dma_chan *chan); + +/* + * stedma40_get_dst_addr - get current destination address + * @chan: the DMA channel + * + * Returns the physical address of the current destination element to be + * written by the DMA. + */ +dma_addr_t stedma40_get_dst_addr(struct dma_chan *chan); + +/** + * stedma40_cyclic_prep_sg - prepare a cyclic DMA transfer + * @chan: the DMA channel to prepare + * @sgl: scatter list + * @sg_len: number of links in the scatter list + * @direction: transfer direction, to or from device + * @dma_flags: DMA_PREP_INTERRUPT if a callback is required after every link. + * See period_callback in struct stedma40_cyclic_desc. + * + * Must be called before trying to start a cyclic DMA transfer. Returns + * ERR_PTR(-errno) on failure. + */ +struct stedma40_cyclic_desc * +stedma40_cyclic_prep_sg(struct dma_chan *chan, + struct scatterlist *sgl, + unsigned int sg_len, + enum dma_data_direction direction, + unsigned long dma_flags); + +/** + * stedma40_cyclic_start - start the cyclic DMA transfer + * @chan: the DMA channel to start + * + * The cyclic DMA must have been prepared earlier with + * stedma40_cyclic_prep_sg(). + */ +int stedma40_cyclic_start(struct dma_chan *chan); + +/** + * stedma40_cyclic_stop() - stop the cyclic DMA transfer + * @chan: the DMA channel to stop + * + * Stops a cyclic DMA transfer which was previously started with + * stedma40_cyclic_start(). + */ +void stedma40_cyclic_stop(struct dma_chan *chan); + +/** + * stedma40_cyclic_free() - free cyclic DMA resources + * @chan: the DMA channel + * + * Must be called to free any resources used for cyclic DMA which have been + * allocated in stedma40_cyclic_prep_sg(). + */ +void stedma40_cyclic_free(struct dma_chan *chan); + +/** + * setdma40_residue() - Returna the remaining bytes to transfer. + * + * @chan: dmaengine handle + * + * returns 0 or positive number of remaning bytes. + */ +u32 stedma40_residue(struct dma_chan *chan); /** * stedma40_filter() - Provides stedma40_chan_cfg to the @@ -169,51 +266,24 @@ struct stedma40_platform_data { * * */ - bool stedma40_filter(struct dma_chan *chan, void *data); /** - * stedma40_slave_mem() - Transfers a raw data buffer to or from a slave - * (=device) + * stedma40_memcpy_sg() - extension of the dma framework, memcpy to/from + * scattergatter lists. * * @chan: dmaengine handle - * @addr: source or destination physicall address. - * @size: bytes to transfer - * @direction: direction of transfer + * @sgl_dst: Destination scatter list + * @sgl_src: Source scatter list + * @sgl_len: The length of each scatterlist. Both lists must be of equal length + * and each element must match the corresponding element in the other scatter + * list. * @flags: is actually enum dma_ctrl_flags. See dmaengine.h */ - -static inline struct -dma_async_tx_descriptor *stedma40_slave_mem(struct dma_chan *chan, - dma_addr_t addr, - unsigned int size, - enum dma_data_direction direction, - unsigned long flags) -{ - struct scatterlist sg; - sg_init_table(&sg, 1); - sg.dma_address = addr; - sg.length = size; - - return chan->device->device_prep_slave_sg(chan, &sg, 1, - direction, flags); -} - -#else -static inline bool stedma40_filter(struct dma_chan *chan, void *data) -{ - return false; -} - -static inline struct -dma_async_tx_descriptor *stedma40_slave_mem(struct dma_chan *chan, - dma_addr_t addr, - unsigned int size, - enum dma_data_direction direction, - unsigned long flags) -{ - return NULL; -} -#endif +struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan, + struct scatterlist *sgl_dst, + struct scatterlist *sgl_src, + unsigned int sgl_len, + unsigned long flags); #endif diff --git a/arch/arm/plat-nomadik/timer.c b/arch/arm/plat-nomadik/timer.c index ef74e157a9d..7ca131e57df 100644 --- a/arch/arm/plat-nomadik/timer.c +++ b/arch/arm/plat-nomadik/timer.c @@ -17,14 +17,64 @@ #include <linux/clk.h> #include <linux/jiffies.h> #include <linux/err.h> +#include <linux/delay.h> #include <linux/sched.h> #include <asm/mach/time.h> #include <asm/sched_clock.h> -#include <plat/mtu.h> +/* + * Guaranteed runtime conversion range in seconds for + * the clocksource and clockevent. + */ +#define MTU_MIN_RANGE 4 + +/* + * The MTU device hosts four different counters, with 4 set of + * registers. These are register names. + */ + +#define MTU_IMSC 0x00 /* Interrupt mask set/clear */ +#define MTU_RIS 0x04 /* Raw interrupt status */ +#define MTU_MIS 0x08 /* Masked interrupt status */ +#define MTU_ICR 0x0C /* Interrupt clear register */ + +/* per-timer registers take 0..3 as argument */ +#define MTU_LR(x) (0x10 + 0x10 * (x) + 0x00) /* Load value */ +#define MTU_VAL(x) (0x10 + 0x10 * (x) + 0x04) /* Current value */ +#define MTU_CR(x) (0x10 + 0x10 * (x) + 0x08) /* Control reg */ +#define MTU_BGLR(x) (0x10 + 0x10 * (x) + 0x0c) /* At next overflow */ + +/* bits for the control register */ +#define MTU_CRn_ENA 0x80 +#define MTU_CRn_PERIODIC 0x40 /* if 0 = free-running */ +#define MTU_CRn_PRESCALE_MASK 0x0c +#define MTU_CRn_PRESCALE_1 0x00 +#define MTU_CRn_PRESCALE_16 0x04 +#define MTU_CRn_PRESCALE_256 0x08 +#define MTU_CRn_32BITS 0x02 +#define MTU_CRn_ONESHOT 0x01 /* if 0 = wraps reloading from BGLR*/ + +/* Other registers are usual amba/primecell registers, currently not used */ +#define MTU_ITCR 0xff0 +#define MTU_ITOP 0xff4 + +#define MTU_PERIPH_ID0 0xfe0 +#define MTU_PERIPH_ID1 0xfe4 +#define MTU_PERIPH_ID2 0xfe8 +#define MTU_PERIPH_ID3 0xfeC + +#define MTU_PCELL0 0xff0 +#define MTU_PCELL1 0xff4 +#define MTU_PCELL2 0xff8 +#define MTU_PCELL3 0xffC + +static bool clkevt_periodic; +static u32 clk_prescale; +static u32 nmdk_cycle; /* write-once */ void __iomem *mtu_base; /* Assigned by machine code */ +#ifdef CONFIG_NOMADIK_MTU_SCHED_CLOCK /* * Override the global weak sched_clock symbol with this * local implementation which uses the clocksource to get some @@ -48,32 +98,56 @@ static void notrace nomadik_update_sched_clock(void) u32 cyc = -readl(mtu_base + MTU_VAL(0)); update_sched_clock(&cd, cyc, (u32)~0); } +#endif /* Clockevent device: use one-shot mode */ +static int nmdk_clkevt_next(unsigned long evt, struct clock_event_device *ev) +{ + writel(1 << 1, mtu_base + MTU_IMSC); + writel(evt, mtu_base + MTU_LR(1)); + /* Load highest value, enable device, enable interrupts */ + writel(MTU_CRn_ONESHOT | clk_prescale | + MTU_CRn_32BITS | MTU_CRn_ENA, + mtu_base + MTU_CR(1)); + + return 0; +} + +void nmdk_clkevt_reset(void) +{ + if (clkevt_periodic) { + + /* Timer: configure load and background-load, and fire it up */ + writel(nmdk_cycle, mtu_base + MTU_LR(1)); + writel(nmdk_cycle, mtu_base + MTU_BGLR(1)); + + writel(MTU_CRn_PERIODIC | clk_prescale | + MTU_CRn_32BITS | MTU_CRn_ENA, + mtu_base + MTU_CR(1)); + writel(1 << 1, mtu_base + MTU_IMSC); + } else { + /* Generate an interrupt to start the clockevent again */ + (void) nmdk_clkevt_next(nmdk_cycle, NULL); + } +} + static void nmdk_clkevt_mode(enum clock_event_mode mode, struct clock_event_device *dev) { - u32 cr; switch (mode) { case CLOCK_EVT_MODE_PERIODIC: - pr_err("%s: periodic mode not supported\n", __func__); + clkevt_periodic = true; + nmdk_clkevt_reset(); break; case CLOCK_EVT_MODE_ONESHOT: - /* Load highest value, enable device, enable interrupts */ - cr = readl(mtu_base + MTU_CR(1)); - writel(0, mtu_base + MTU_LR(1)); - writel(cr | MTU_CRn_ENA, mtu_base + MTU_CR(1)); - writel(1 << 1, mtu_base + MTU_IMSC); + clkevt_periodic = false; break; case CLOCK_EVT_MODE_SHUTDOWN: case CLOCK_EVT_MODE_UNUSED: - /* disable irq */ writel(0, mtu_base + MTU_IMSC); /* disable timer */ - cr = readl(mtu_base + MTU_CR(1)); - cr &= ~MTU_CRn_ENA; - writel(cr, mtu_base + MTU_CR(1)); + writel(0, mtu_base + MTU_CR(1)); /* load some high default value */ writel(0xffffffff, mtu_base + MTU_LR(1)); break; @@ -82,21 +156,36 @@ static void nmdk_clkevt_mode(enum clock_event_mode mode, } } -static int nmdk_clkevt_next(unsigned long evt, struct clock_event_device *ev) -{ - /* writing the value has immediate effect */ - writel(evt, mtu_base + MTU_LR(1)); - return 0; -} - static struct clock_event_device nmdk_clkevt = { .name = "mtu_1", - .features = CLOCK_EVT_FEAT_ONESHOT, + .features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC, .rating = 200, .set_mode = nmdk_clkevt_mode, .set_next_event = nmdk_clkevt_next, }; +#ifdef ARCH_HAS_READ_CURRENT_TIMER +static void nmdk_timer_delay_loop(unsigned long loops) +{ + unsigned long bclock, now; + + bclock = ~readl(mtu_base + MTU_VAL(0)); + do { + now = ~readl(mtu_base + MTU_VAL(0)); + /* If timer have been cleared (suspend) or wrapped we exit */ + if (unlikely(now < bclock)) + return; + } while ((now - bclock) < loops); +} + +/* Used to calibrate the delay */ +int read_current_timer(unsigned long *timer_val) +{ + *timer_val = ~readl(mtu_base + MTU_VAL(0)); + return 0; +} +#endif + /* * IRQ Handler for timer 1 of the MTU block. */ @@ -116,11 +205,28 @@ static struct irqaction nmdk_timer_irq = { .dev_id = &nmdk_clkevt, }; +void nmdk_clksrc_reset(void) +{ + /* Disable */ + writel(0, mtu_base + MTU_CR(0)); + + /* ClockSource: configure load and background-load, and fire it up */ + writel(nmdk_cycle, mtu_base + MTU_LR(0)); + writel(nmdk_cycle, mtu_base + MTU_BGLR(0)); + + writel(clk_prescale | MTU_CRn_32BITS | MTU_CRn_ENA, + mtu_base + MTU_CR(0)); +} + +struct clock_event_device *nmdk_clkevt_get(void) +{ + return &nmdk_clkevt; +} + void __init nmdk_timer_init(void) { unsigned long rate; struct clk *clk0; - u32 cr = MTU_CRn_32BITS; clk0 = clk_get_sys("mtu0", NULL); BUG_ON(IS_ERR(clk0)); @@ -138,30 +244,28 @@ void __init nmdk_timer_init(void) rate = clk_get_rate(clk0); if (rate > 32000000) { rate /= 16; - cr |= MTU_CRn_PRESCALE_16; + clk_prescale = MTU_CRn_PRESCALE_16; } else { - cr |= MTU_CRn_PRESCALE_1; + clk_prescale = MTU_CRn_PRESCALE_1; } + nmdk_cycle = (rate + HZ/2) / HZ; + + /* Timer 0 is the free running clocksource */ - writel(cr, mtu_base + MTU_CR(0)); - writel(0, mtu_base + MTU_LR(0)); - writel(0, mtu_base + MTU_BGLR(0)); - writel(cr | MTU_CRn_ENA, mtu_base + MTU_CR(0)); + nmdk_clksrc_reset(); if (clocksource_mmio_init(mtu_base + MTU_VAL(0), "mtu_0", rate, 200, 32, clocksource_mmio_readl_down)) pr_err("timer: failed to initialize clock source %s\n", "mtu_0"); - +#ifdef CONFIG_NOMADIK_MTU_SCHED_CLOCK init_sched_clock(&cd, nomadik_update_sched_clock, 32, rate); - +#endif /* Timer 1 is used for events */ clockevents_calc_mult_shift(&nmdk_clkevt, rate, MTU_MIN_RANGE); - writel(cr | MTU_CRn_ONESHOT, mtu_base + MTU_CR(1)); /* off, currently */ - nmdk_clkevt.max_delta_ns = clockevent_delta2ns(0xffffffff, &nmdk_clkevt); nmdk_clkevt.min_delta_ns = @@ -171,4 +275,8 @@ void __init nmdk_timer_init(void) /* Register irq and clockevents */ setup_irq(IRQ_MTU0, &nmdk_timer_irq); clockevents_register_device(&nmdk_clkevt); +#ifdef ARCH_HAS_READ_CURRENT_TIMER + set_delay_fn(nmdk_timer_delay_loop); +#endif + } diff --git a/drivers/amba/bus.c b/drivers/amba/bus.c index d74926e0939..6e4b1553afe 100644 --- a/drivers/amba/bus.c +++ b/drivers/amba/bus.c @@ -109,7 +109,7 @@ static int amba_legacy_resume(struct device *dev) return ret; } -static int amba_pm_prepare(struct device *dev) +int amba_pm_prepare(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -120,7 +120,7 @@ static int amba_pm_prepare(struct device *dev) return ret; } -static void amba_pm_complete(struct device *dev) +void amba_pm_complete(struct device *dev) { struct device_driver *drv = dev->driver; @@ -137,7 +137,7 @@ static void amba_pm_complete(struct device *dev) #ifdef CONFIG_SUSPEND -static int amba_pm_suspend(struct device *dev) +int amba_pm_suspend(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -155,7 +155,7 @@ static int amba_pm_suspend(struct device *dev) return ret; } -static int amba_pm_suspend_noirq(struct device *dev) +int amba_pm_suspend_noirq(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -171,7 +171,7 @@ static int amba_pm_suspend_noirq(struct device *dev) return ret; } -static int amba_pm_resume(struct device *dev) +int amba_pm_resume(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -189,7 +189,7 @@ static int amba_pm_resume(struct device *dev) return ret; } -static int amba_pm_resume_noirq(struct device *dev) +int amba_pm_resume_noirq(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -216,7 +216,7 @@ static int amba_pm_resume_noirq(struct device *dev) #ifdef CONFIG_HIBERNATE_CALLBACKS -static int amba_pm_freeze(struct device *dev) +int amba_pm_freeze(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -234,7 +234,7 @@ static int amba_pm_freeze(struct device *dev) return ret; } -static int amba_pm_freeze_noirq(struct device *dev) +int amba_pm_freeze_noirq(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -250,7 +250,7 @@ static int amba_pm_freeze_noirq(struct device *dev) return ret; } -static int amba_pm_thaw(struct device *dev) +int amba_pm_thaw(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -268,7 +268,7 @@ static int amba_pm_thaw(struct device *dev) return ret; } -static int amba_pm_thaw_noirq(struct device *dev) +int amba_pm_thaw_noirq(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -284,7 +284,7 @@ static int amba_pm_thaw_noirq(struct device *dev) return ret; } -static int amba_pm_poweroff(struct device *dev) +int amba_pm_poweroff(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -302,7 +302,7 @@ static int amba_pm_poweroff(struct device *dev) return ret; } -static int amba_pm_poweroff_noirq(struct device *dev) +int amba_pm_poweroff_noirq(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -318,7 +318,7 @@ static int amba_pm_poweroff_noirq(struct device *dev) return ret; } -static int amba_pm_restore(struct device *dev) +int amba_pm_restore(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -336,7 +336,7 @@ static int amba_pm_restore(struct device *dev) return ret; } -static int amba_pm_restore_noirq(struct device *dev) +int amba_pm_restore_noirq(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -368,20 +368,7 @@ static int amba_pm_restore_noirq(struct device *dev) #ifdef CONFIG_PM static const struct dev_pm_ops amba_pm = { - .prepare = amba_pm_prepare, - .complete = amba_pm_complete, - .suspend = amba_pm_suspend, - .resume = amba_pm_resume, - .freeze = amba_pm_freeze, - .thaw = amba_pm_thaw, - .poweroff = amba_pm_poweroff, - .restore = amba_pm_restore, - .suspend_noirq = amba_pm_suspend_noirq, - .resume_noirq = amba_pm_resume_noirq, - .freeze_noirq = amba_pm_freeze_noirq, - .thaw_noirq = amba_pm_thaw_noirq, - .poweroff_noirq = amba_pm_poweroff_noirq, - .restore_noirq = amba_pm_restore_noirq, + USE_AMBA_PM_SLEEP_OPS SET_RUNTIME_PM_OPS( pm_generic_runtime_suspend, pm_generic_runtime_resume, diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 21cf46f4524..95f10c51f23 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -172,6 +172,9 @@ config SYS_HYPERVISOR bool default n +config SYS_SOC + bool + source "drivers/base/regmap/Kconfig" endmenu diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 99a375ad2cc..442251cf83e 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -17,6 +17,7 @@ ifeq ($(CONFIG_SYSFS),y) obj-$(CONFIG_MODULES) += module.o endif obj-$(CONFIG_SYS_HYPERVISOR) += hypervisor.o +obj-$(CONFIG_SYS_SOC) += soc.o obj-$(CONFIG_REGMAP) += regmap/ ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG diff --git a/drivers/base/soc.c b/drivers/base/soc.c new file mode 100644 index 00000000000..046b43bfcdb --- /dev/null +++ b/drivers/base/soc.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Maxime Coquelin <maxime.coquelin-nonst@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/sysfs.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/stat.h> +#include <linux/slab.h> +#include <linux/sys_soc.h> + +struct kobject *soc_object; + +ssize_t show_soc_info(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct sysfs_soc_info *si = container_of(attr, + struct sysfs_soc_info, attr); + + if (si->info) + return sprintf(buf, "%s\n", si->info); + + return si->get_info(buf, si); +} + +int __init register_sysfs_soc_info(struct sysfs_soc_info *info, int nb_info) +{ + int i, ret; + + for (i = 0; i < nb_info; i++) { + ret = sysfs_create_file(soc_object, &info[i].attr.attr); + if (ret) { + for (i -= 1; i >= 0; i--) + sysfs_remove_file(soc_object, &info[i].attr.attr); + break; + } + } + + return ret; +} + +static struct attribute *soc_attrs[] = { + NULL, +}; + +static struct attribute_group soc_attr_group = { + .attrs = soc_attrs, +}; + +int __init register_sysfs_soc(struct sysfs_soc_info *info, size_t num) +{ + int ret; + + soc_object = kobject_create_and_add("socinfo", NULL); + if (!soc_object) { + ret = -ENOMEM; + goto exit; + } + + ret = sysfs_create_group(soc_object, &soc_attr_group); + if (ret) + goto kset_exit; + + ret = register_sysfs_soc_info(info, num); + if (ret) + goto group_exit; + + return 0; + +group_exit: + sysfs_remove_group(soc_object, &soc_attr_group); +kset_exit: + kobject_put(soc_object); +exit: + return ret; +} + diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 34e9c4f8892..999d6a03e43 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -15,3 +15,18 @@ config CLKSRC_MMIO config DW_APB_TIMER bool + +config CLKSRC_DBX500_PRCMU + bool "Clocksource PRCMU Timer" + depends on UX500_SOC_DB5500 || UX500_SOC_DB8500 + default y + help + Use the always on PRCMU Timer as clocksource + +config CLKSRC_DBX500_PRCMU_SCHED_CLOCK + bool "Clocksource PRCMU Timer sched_clock" + depends on (CLKSRC_DBX500_PRCMU && !NOMADIK_MTU_SCHED_CLOCK) + select HAVE_SCHED_CLOCK + default y + help + Use the always on PRCMU Timer as sched_clock diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 85ad1646a7b..6348caf5490 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_SH_TIMER_TMU) += sh_tmu.o obj-$(CONFIG_CLKBLD_I8253) += i8253.o obj-$(CONFIG_CLKSRC_MMIO) += mmio.o obj-$(CONFIG_DW_APB_TIMER) += dw_apb_timer.o +obj-$(CONFIG_CLKSRC_DBX500_PRCMU) += clksrc-dbx500-prcmu.o diff --git a/drivers/clocksource/clksrc-dbx500-prcmu.c b/drivers/clocksource/clksrc-dbx500-prcmu.c new file mode 100644 index 00000000000..99fbda39ab7 --- /dev/null +++ b/drivers/clocksource/clksrc-dbx500-prcmu.c @@ -0,0 +1,127 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Author: Mattias Wallin <mattias.wallin@stericsson.com> for ST-Ericsson + * Author: Sundar Iyer for ST-Ericsson + * sched_clock implementation is based on: + * plat-nomadik/timer.c Linus Walleij <linus.walleij@stericsson.com> + * + * DBx500-PRCMU Timer + * The PRCMU has 5 timers which are available in a always-on + * power domain. We use the Timer 4 for our always-on clock + * source on DB8500 and Timer 3 on DB5500. + */ +#include <linux/clockchips.h> +#include <linux/clksrc-dbx500-prcmu.h> +#ifdef CONFIG_BOOTTIME +#include <linux/boottime.h> +#endif + +#include <asm/sched_clock.h> + +#include <mach/setup.h> +#include <mach/hardware.h> + +#define RATE_32K 32768 + +#define TIMER_MODE_CONTINOUS 0x1 +#define TIMER_DOWNCOUNT_VAL 0xffffffff + +#define PRCMU_TIMER_REF 0 +#define PRCMU_TIMER_DOWNCOUNT 0x4 +#define PRCMU_TIMER_MODE 0x8 + +#define SCHED_CLOCK_MIN_WRAP 131072 /* 2^32 / 32768 */ + +void __iomem *clksrc_dbx500_timer_base; + +static cycle_t clksrc_dbx500_prcmu_read(struct clocksource *cs) +{ + u32 count, count2; + + do { + count = readl(clksrc_dbx500_timer_base + + PRCMU_TIMER_DOWNCOUNT); + count2 = readl(clksrc_dbx500_timer_base + + PRCMU_TIMER_DOWNCOUNT); + } while (count2 != count); + + /* Negate because the timer is a decrementing counter */ + return ~count; +} + +static struct clocksource clocksource_dbx500_prcmu = { + .name = "dbx500-prcmu-timer", + .rating = 300, + .read = clksrc_dbx500_prcmu_read, + .shift = 10, + .mask = CLOCKSOURCE_MASK(32), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +#ifdef CONFIG_CLKSRC_DBX500_PRCMU_SCHED_CLOCK +static DEFINE_CLOCK_DATA(cd); + +unsigned long long notrace sched_clock(void) +{ + u32 cyc; + + if (unlikely(!clksrc_dbx500_timer_base)) + return 0; + + cyc = clksrc_dbx500_prcmu_read(&clocksource_dbx500_prcmu); + + return cyc_to_sched_clock(&cd, cyc, (u32)~0); +} + +static void notrace clksrc_dbx500_prcmu_update_sched_clock(void) +{ + u32 cyc = clksrc_dbx500_prcmu_read(&clocksource_dbx500_prcmu); + update_sched_clock(&cd, cyc, (u32)~0); +} +#endif + +#ifdef CONFIG_BOOTTIME +static unsigned long __init boottime_get_time(void) +{ + return div_s64(clocksource_cyc2ns(clocksource_dbx500_prcmu.read( + &clocksource_dbx500_prcmu), + clocksource_dbx500_prcmu.mult, + clocksource_dbx500_prcmu.shift), + 1000); +} + +static struct boottime_timer __initdata boottime_timer = { + .init = NULL, + .get_time = boottime_get_time, + .finalize = NULL, +}; +#endif + +void __init clksrc_dbx500_prcmu_init(void) +{ + /* + * The A9 sub system expects the timer to be configured as + * a continous looping timer. + * The PRCMU should configure it but if it for some reason + * don't we do it here. + */ + if (readl(clksrc_dbx500_timer_base + PRCMU_TIMER_MODE) != + TIMER_MODE_CONTINOUS) { + writel(TIMER_MODE_CONTINOUS, + clksrc_dbx500_timer_base + PRCMU_TIMER_MODE); + writel(TIMER_DOWNCOUNT_VAL, + clksrc_dbx500_timer_base + PRCMU_TIMER_REF); + } +#ifdef CONFIG_CLKSRC_DBX500_PRCMU_SCHED_CLOCK + init_sched_clock(&cd, clksrc_dbx500_prcmu_update_sched_clock, + 32, RATE_32K); +#endif + clocksource_calc_mult_shift(&clocksource_dbx500_prcmu, + RATE_32K, SCHED_CLOCK_MIN_WRAP); + clocksource_register(&clocksource_dbx500_prcmu); +#ifdef CONFIG_BOOTTIME + boottime_activate(&boottime_timer); +#endif +} diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index a48bc02cd76..2988ff59bdb 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -39,7 +39,8 @@ obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o ################################################################################## # ARM SoC drivers -obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o +obj-$(CONFIG_UX500_SOC_DB8500) += dbx500-cpufreq.o +obj-$(CONFIG_UX500_SOC_DB5500) += dbx500-cpufreq.o obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 987a165ede2..d0945dbb29c 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -368,6 +368,27 @@ show_one(scaling_cur_freq, cur); static int __cpufreq_set_policy(struct cpufreq_policy *data, struct cpufreq_policy *policy); +int cpufreq_update_freq(int cpu, unsigned int min, unsigned int max) +{ + int ret; + struct cpufreq_policy new_policy; + struct cpufreq_policy *policy = cpufreq_cpu_get(cpu); + + ret = cpufreq_get_policy(&new_policy, cpu); + if (ret) + return -EINVAL; + + new_policy.min = min; + new_policy.max = max; + + ret = __cpufreq_set_policy(policy, &new_policy); + policy->user_policy.min = policy->min; + policy->user_policy.max = policy->max; + + return ret; +} +EXPORT_SYMBOL(cpufreq_update_freq); + /** * cpufreq_per_cpu_attr_write() / store_##file_name() - sysfs write access */ diff --git a/drivers/cpufreq/db8500-cpufreq.c b/drivers/cpufreq/db8500-cpufreq.c deleted file mode 100644 index d90456a809f..00000000000 --- a/drivers/cpufreq/db8500-cpufreq.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) STMicroelectronics 2009 - * Copyright (C) ST-Ericsson SA 2010 - * - * License Terms: GNU General Public License v2 - * Author: Sundar Iyer <sundar.iyer@stericsson.com> - * Author: Martin Persson <martin.persson@stericsson.com> - * Author: Jonas Aaberg <jonas.aberg@stericsson.com> - * - */ -#include <linux/kernel.h> -#include <linux/cpufreq.h> -#include <linux/delay.h> -#include <linux/slab.h> -#include <linux/mfd/db8500-prcmu.h> -#include <mach/id.h> - -static struct cpufreq_frequency_table freq_table[] = { - [0] = { - .index = 0, - .frequency = 300000, - }, - [1] = { - .index = 1, - .frequency = 600000, - }, - [2] = { - /* Used for MAX_OPP, if available */ - .index = 2, - .frequency = CPUFREQ_TABLE_END, - }, - [3] = { - .index = 3, - .frequency = CPUFREQ_TABLE_END, - }, -}; - -static enum arm_opp idx2opp[] = { - ARM_50_OPP, - ARM_100_OPP, - ARM_MAX_OPP -}; - -static struct freq_attr *db8500_cpufreq_attr[] = { - &cpufreq_freq_attr_scaling_available_freqs, - NULL, -}; - -static int db8500_cpufreq_verify_speed(struct cpufreq_policy *policy) -{ - return cpufreq_frequency_table_verify(policy, freq_table); -} - -static int db8500_cpufreq_target(struct cpufreq_policy *policy, - unsigned int target_freq, - unsigned int relation) -{ - struct cpufreq_freqs freqs; - unsigned int idx; - - /* scale the target frequency to one of the extremes supported */ - if (target_freq < policy->cpuinfo.min_freq) - target_freq = policy->cpuinfo.min_freq; - if (target_freq > policy->cpuinfo.max_freq) - target_freq = policy->cpuinfo.max_freq; - - /* Lookup the next frequency */ - if (cpufreq_frequency_table_target - (policy, freq_table, target_freq, relation, &idx)) { - return -EINVAL; - } - - freqs.old = policy->cur; - freqs.new = freq_table[idx].frequency; - freqs.cpu = policy->cpu; - - if (freqs.old == freqs.new) - return 0; - - /* pre-change notification */ - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - - /* request the PRCM unit for opp change */ - if (prcmu_set_arm_opp(idx2opp[idx])) { - pr_err("db8500-cpufreq: Failed to set OPP level\n"); - return -EINVAL; - } - - /* post change notification */ - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - - return 0; -} - -static unsigned int db8500_cpufreq_getspeed(unsigned int cpu) -{ - int i; - /* request the prcm to get the current ARM opp */ - for (i = 0; prcmu_get_arm_opp() != idx2opp[i]; i++) - ; - return freq_table[i].frequency; -} - -static int __cpuinit db8500_cpufreq_init(struct cpufreq_policy *policy) -{ - int res; - int i; - - BUILD_BUG_ON(ARRAY_SIZE(idx2opp) + 1 != ARRAY_SIZE(freq_table)); - - if (cpu_is_u8500v2() && !prcmu_is_u8400()) { - freq_table[0].frequency = 400000; - freq_table[1].frequency = 800000; - if (prcmu_has_arm_maxopp()) - freq_table[2].frequency = 1000000; - } - - /* get policy fields based on the table */ - res = cpufreq_frequency_table_cpuinfo(policy, freq_table); - if (!res) - cpufreq_frequency_table_get_attr(freq_table, policy->cpu); - else { - pr_err("db8500-cpufreq : Failed to read policy table\n"); - return res; - } - - policy->min = policy->cpuinfo.min_freq; - policy->max = policy->cpuinfo.max_freq; - policy->cur = db8500_cpufreq_getspeed(policy->cpu); - - for (i = 0; freq_table[i].frequency != policy->cur; i++) - ; - - policy->governor = CPUFREQ_DEFAULT_GOVERNOR; - - /* - * FIXME : Need to take time measurement across the target() - * function with no/some/all drivers in the notification - * list. - */ - policy->cpuinfo.transition_latency = 20 * 1000; /* in ns */ - - /* policy sharing between dual CPUs */ - cpumask_copy(policy->cpus, &cpu_present_map); - - policy->shared_type = CPUFREQ_SHARED_TYPE_ALL; - - return 0; -} - -static struct cpufreq_driver db8500_cpufreq_driver = { - .flags = CPUFREQ_STICKY, - .verify = db8500_cpufreq_verify_speed, - .target = db8500_cpufreq_target, - .get = db8500_cpufreq_getspeed, - .init = db8500_cpufreq_init, - .name = "DB8500", - .attr = db8500_cpufreq_attr, -}; - -static int __init db8500_cpufreq_register(void) -{ - if (!cpu_is_u8500v20_or_later()) - return -ENODEV; - - pr_info("cpufreq for DB8500 started\n"); - return cpufreq_register_driver(&db8500_cpufreq_driver); -} -device_initcall(db8500_cpufreq_register); diff --git a/drivers/cpufreq/dbx500-cpufreq.c b/drivers/cpufreq/dbx500-cpufreq.c new file mode 100644 index 00000000000..1f9249f869a --- /dev/null +++ b/drivers/cpufreq/dbx500-cpufreq.c @@ -0,0 +1,303 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * Author: Sundar Iyer + * Author: Martin Persson + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + */ + +#include <linux/kernel.h> +#include <linux/cpufreq.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <mach/id.h> + +static struct cpufreq_frequency_table db8500_freq_table[] = { + [0] = { + .index = 0, + .frequency = 200000, + }, + [1] = { + .index = 1, + .frequency = 300000, + }, + [2] = { + .index = 2, + .frequency = 600000, + }, + [3] = { + /* Used for MAX_OPP, if available */ + .index = 3, + .frequency = CPUFREQ_TABLE_END, + }, + [4] = { + .index = 4, + .frequency = CPUFREQ_TABLE_END, + }, +}; + +static struct cpufreq_frequency_table db5500_freq_table[] = { + [0] = { + .index = 0, + .frequency = 200000, + }, + [1] = { + .index = 1, + .frequency = 396500, + }, + [2] = { + .index = 2, + .frequency = 793000, + }, + [3] = { + .index = 3, + .frequency = CPUFREQ_TABLE_END, + }, +}; + +static struct cpufreq_frequency_table *freq_table; + +static enum arm_opp db8500_idx2opp[] = { + ARM_EXTCLK, + ARM_50_OPP, + ARM_100_OPP, + ARM_MAX_OPP +}; + +static enum arm_opp db5500_idx2opp[] = { + ARM_EXTCLK, + ARM_50_OPP, + ARM_100_OPP, +}; + +static enum arm_opp *idx2opp; + +static struct freq_attr *dbx500_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static int dbx500_cpufreq_verify_speed(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, freq_table); +} + +static int dbx500_cpufreq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + struct cpufreq_freqs freqs; + unsigned int idx; + + /* scale the target frequency to one of the extremes supported */ + if (target_freq < policy->cpuinfo.min_freq) + target_freq = policy->cpuinfo.min_freq; + if (target_freq > policy->cpuinfo.max_freq) + target_freq = policy->cpuinfo.max_freq; + + /* Lookup the next frequency */ + if (cpufreq_frequency_table_target + (policy, freq_table, target_freq, relation, &idx)) { + return -EINVAL; + } + + freqs.old = policy->cur; + freqs.new = freq_table[idx].frequency; + + if (freqs.old == freqs.new) + return 0; + + /* pre-change notification */ + for_each_cpu(freqs.cpu, policy->cpus) + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + /* request the PRCM unit for opp change */ + if (prcmu_set_arm_opp(idx2opp[idx])) { + pr_err("ux500-cpufreq: Failed to set OPP level\n"); + return -EINVAL; + } + + /* post change notification */ + for_each_cpu(freqs.cpu, policy->cpus) + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + +static unsigned int dbx500_cpufreq_getspeed(unsigned int cpu) +{ + int i; + /* request the prcm to get the current ARM opp */ + for (i = 0; prcmu_get_arm_opp() != idx2opp[i]; i++) + ; + return freq_table[i].frequency; +} + +static bool initialized; + +static void __init dbx500_cpufreq_early_init(void) +{ + if (cpu_is_u5500()) { + freq_table = db5500_freq_table; + idx2opp = db5500_idx2opp; + + } else if (cpu_is_u8500()) { + freq_table = db8500_freq_table; + idx2opp = db8500_idx2opp; + + if (!prcmu_is_u8400()) { + freq_table[1].frequency = 400000; + freq_table[2].frequency = 800000; + if (prcmu_has_arm_maxopp()) + freq_table[3].frequency = 1000000; + } + + } else { + ux500_unknown_soc(); + } + + initialized = true; +} + +/* + * This is called from localtimer initialization, via the clk_get_rate() for + * the smp_twd clock. This is way before cpufreq is initialized. + */ +unsigned long dbx500_cpufreq_getfreq(void) +{ + if (!initialized) + dbx500_cpufreq_early_init(); + + return dbx500_cpufreq_getspeed(0) * 1000; +} + +int dbx500_cpufreq_get_limits(int cpu, int r, + unsigned int *min, unsigned int *max) +{ + int op; + int i; + int ret; + static int old_freq; + struct cpufreq_policy p; + + switch (r) { + case 0: + /* Fall through */ + case 25: + op = ARM_EXTCLK; + break; + case 50: + op = ARM_50_OPP; + break; + case 100: + op = ARM_100_OPP; + break; + case 125: + if (cpu_is_u8500() && prcmu_has_arm_maxopp()) + op = ARM_MAX_OPP; + else + op = ARM_100_OPP; + break; + default: + pr_err("cpufreq-dbx500: Incorrect arm target value (%d).\n", + r); + BUG(); + break; + } + + for (i = 0; idx2opp[i] != op; i++) + ; + + if (freq_table[i].frequency == CPUFREQ_TABLE_END) { + pr_err("cpufreq-dbx500: Minimum frequency does not exist!\n"); + BUG(); + } + + if (freq_table[i].frequency != old_freq) + pr_debug("cpufreq-dbx500: set min arm freq to %d\n", + freq_table[i].frequency); + + (*min) = freq_table[i].frequency; + + ret = cpufreq_get_policy(&p, cpu); + if (ret) { + pr_err("cpufreq-dbx500: Failed to get policy.\n"); + return -EINVAL; + } + + (*max) = p.max; + return 0; +} + +static int __cpuinit dbx500_cpufreq_init(struct cpufreq_policy *policy) +{ + int res; + int i; + + /* get policy fields based on the table */ + res = cpufreq_frequency_table_cpuinfo(policy, freq_table); + if (!res) + cpufreq_frequency_table_get_attr(freq_table, policy->cpu); + else { + pr_err("dbx500-cpufreq : Failed to read policy table\n"); + return res; + } + + policy->min = policy->cpuinfo.min_freq; + policy->max = policy->cpuinfo.max_freq; + policy->cur = dbx500_cpufreq_getspeed(policy->cpu); + + for (i = 0; freq_table[i].frequency != policy->cur; i++) + ; + + policy->governor = CPUFREQ_DEFAULT_GOVERNOR; + + /* + * FIXME : Need to take time measurement across the target() + * function with no/some/all drivers in the notification + * list. + */ + policy->cpuinfo.transition_latency = 20 * 1000; /* in ns */ + + /* policy sharing between dual CPUs */ + cpumask_copy(policy->cpus, &cpu_present_map); + + policy->shared_type = CPUFREQ_SHARED_TYPE_ALL; + + return 0; +} + +static struct cpufreq_driver dbx500_cpufreq_driver = { + .flags = CPUFREQ_STICKY, + .verify = dbx500_cpufreq_verify_speed, + .target = dbx500_cpufreq_target, + .get = dbx500_cpufreq_getspeed, + .init = dbx500_cpufreq_init, + .name = "DBX500", + .attr = dbx500_cpufreq_attr, +}; + +static int __init dbx500_cpufreq_register(void) +{ + int i; + + if (cpu_is_u5500() && cpu_is_u5500v1()) + return -ENODEV; + + if (cpu_is_u8500() && !cpu_is_u8500v20_or_later()) + return -ENODEV; + + if (!initialized) + dbx500_cpufreq_early_init(); + + pr_info("dbx500-cpufreq : Available frequencies:\n"); + + for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) + pr_info(" %d Mhz\n", freq_table[i].frequency / 1000); + + return cpufreq_register_driver(&dbx500_cpufreq_driver); +} +device_initcall(dbx500_cpufreq_register); diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index 7dbc4a83c45..0e07e037933 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -2,6 +2,7 @@ config CPU_IDLE bool "CPU idle PM support" default ACPI + depends on PM help CPU idle is a generic framework for supporting software-controlled idle processor power management. It includes modular cross-platform @@ -18,3 +19,35 @@ config CPU_IDLE_GOV_MENU bool depends on CPU_IDLE && NO_HZ default y + +config UX500_CPUIDLE + tristate "CPUIdle support" + depends on CPU_IDLE && (MFD_DB5500_PRCMU || MFD_DB8500_PRCMU) && !RTC_DRV_PL031 && PM + default y + select GENERIC_CLOCKEVENTS_BROADCAST + select UX500_CONTEXT + help + Add support for CPUIdle for U8500. + +config U8500_CPUIDLE_DEEPEST_STATE + int "Deepest sleep state" + default 4 if UX500_SOC_DB8500 + default 1 if UX500_SOC_DB5500 + depends on UX500_CPUIDLE + help + Set deepest sleep state. See the cstate struct in cpuidle.c. + Default is ApSleep. + +config UX500_CPUIDLE_APDEEPIDLE + bool "CPUIdle ApDeepIdle" + depends on UX500_CPUIDLE + help + Adds the power level ApDeepIdle, where APE is powered on while + ARM is powered off. Default n. + +config UX500_CPUIDLE_DEBUG + bool "CPUIdle debug" + depends on UX500_CPUIDLE && DEBUG_FS + help + Add debugging support for CPUIdle for Ux500. + diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 5634f88379d..8c8a1324b1f 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -3,3 +3,6 @@ # obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ + +obj-$(CONFIG_UX500_CPUIDLE) += cpuidle-dbx500.o +obj-$(CONFIG_UX500_CPUIDLE_DEBUG) += cpuidle-dbx500_dbg.o diff --git a/drivers/cpuidle/cpuidle-dbx500.c b/drivers/cpuidle/cpuidle-dbx500.c new file mode 100644 index 00000000000..59f74a1b98f --- /dev/null +++ b/drivers/cpuidle/cpuidle-dbx500.c @@ -0,0 +1,714 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * Author: Rickard Andersson <rickard.andersson@stericsson.com>, + * Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson. + * + * Loosely based on cpuidle.c by Sundar Iyer. + * + * License terms: GNU General Public License (GPL) version 2 + * + */ +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/tick.h> +#include <linux/clockchips.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/regulator/db8500-prcmu.h> + +#include <mach/pm.h> +#include <mach/pm-timer.h> +#include <mach/context.h> + +#include <plat/mtu.h> + +#include "cpuidle-dbx500.h" +#include "cpuidle-dbx500_dbg.h" + +/* + * All measurements are with two cpus online (worst case) and at + * 200 MHz (worst case) + * + * Enter latency depends on cpu frequency, and is only depending on + * code executing on the ARM. + * Exit latency is both depending on "wake latency" which is the + * time between the PRCMU has gotten the interrupt and the ARM starts + * to execute and the time before everything is done on the ARM. + * The wake latency is more or less constant related to cpu frequency, + * but can differ depending on what the modem does. + * Wake latency is not included for plain WFI. + * For states that uses RTC (Sleep & DeepSleep), wake latency is reduced + * from clock programming timeout. + * + */ +#define DEEP_SLEEP_WAKE_UP_LATENCY 8500 +/* Wake latency from ApSleep is measured to be around 1.0 to 1.5 ms */ +#define MIN_SLEEP_WAKE_UP_LATENCY 1000 +#define MAX_SLEEP_WAKE_UP_LATENCY 1500 + +#define UL_PLL_START_UP_LATENCY 8000 /* us */ + +static struct cstate cstates[] = { + { + .enter_latency = 0, + .exit_latency = 0, + .threshold = 0, + .power_usage = 1000, + .APE = APE_ON, + .ARM = ARM_ON, + .UL_PLL = UL_PLL_ON, + .ESRAM = ESRAM_RET, + .pwrst = PRCMU_AP_NO_CHANGE, + .state = CI_RUNNING, + .desc = "Running ", + }, + { + /* These figures are not really true. There is a cost for WFI */ + .enter_latency = 0, + .exit_latency = 0, + .threshold = 0, + .power_usage = 10, + .APE = APE_ON, + .ARM = ARM_ON, + .UL_PLL = UL_PLL_ON, + .ESRAM = ESRAM_RET, + .pwrst = PRCMU_AP_NO_CHANGE, + .flags = CPUIDLE_FLAG_TIME_VALID, + .state = CI_WFI, + .desc = "Wait for interrupt ", + }, + { + .enter_latency = 170, + .exit_latency = 70, + .threshold = 260, + .power_usage = 4, + .APE = APE_ON, + .ARM = ARM_RET, + .UL_PLL = UL_PLL_ON, + .ESRAM = ESRAM_RET, + .pwrst = PRCMU_AP_IDLE, + .flags = CPUIDLE_FLAG_TIME_VALID, + .state = CI_IDLE, + .desc = "ApIdle ", + }, + { + .enter_latency = 350, + .exit_latency = MAX_SLEEP_WAKE_UP_LATENCY + 200, + /* + * Note: Sleep time must be longer than 120 us or else + * there might be issues with the RTC-RTT block. + */ + .threshold = MAX_SLEEP_WAKE_UP_LATENCY + 350 + 200, + .power_usage = 3, + .APE = APE_OFF, + .ARM = ARM_RET, + .UL_PLL = UL_PLL_ON, + .ESRAM = ESRAM_RET, + .pwrst = PRCMU_AP_SLEEP, + .flags = CPUIDLE_FLAG_TIME_VALID, + .state = CI_SLEEP, + .desc = "ApSleep ", + }, + { + .enter_latency = 350, + .exit_latency = (MAX_SLEEP_WAKE_UP_LATENCY + + UL_PLL_START_UP_LATENCY + 200), + .threshold = (MAX_SLEEP_WAKE_UP_LATENCY + + UL_PLL_START_UP_LATENCY + 350 + 200), + .power_usage = 2, + .APE = APE_OFF, + .ARM = ARM_RET, + .UL_PLL = UL_PLL_OFF, + .ESRAM = ESRAM_RET, + .pwrst = PRCMU_AP_SLEEP, + .flags = CPUIDLE_FLAG_TIME_VALID, + .state = CI_SLEEP, + .desc = "ApSleep, UL PLL off ", + }, +#ifdef CONFIG_UX500_CPUIDLE_APDEEPIDLE + { + .enter_latency = 400, + .exit_latency = DEEP_SLEEP_WAKE_UP_LATENCY + 400, + .threshold = DEEP_SLEEP_WAKE_UP_LATENCY + 400 + 400, + .power_usage = 2, + .APE = APE_ON, + .ARM = ARM_OFF, + .UL_PLL = UL_PLL_ON, + .ESRAM = ESRAM_RET, + .pwrst = PRCMU_AP_DEEP_IDLE, + .flags = CPUIDLE_FLAG_TIME_VALID, + .state = CI_DEEP_IDLE, + .desc = "ApDeepIdle, UL PLL off ", + }, +#endif + { + .enter_latency = 410, + .exit_latency = DEEP_SLEEP_WAKE_UP_LATENCY + 420, + .threshold = DEEP_SLEEP_WAKE_UP_LATENCY + 410 + 420, + .power_usage = 1, + .APE = APE_OFF, + .ARM = ARM_OFF, + .UL_PLL = UL_PLL_OFF, + .ESRAM = ESRAM_RET, + .pwrst = PRCMU_AP_DEEP_SLEEP, + .flags = CPUIDLE_FLAG_TIME_VALID, + .state = CI_DEEP_SLEEP, + .desc = "ApDeepsleep, UL PLL off", + }, +}; + +struct cpu_state { + int gov_cstate; + ktime_t sched_wake_up; + struct cpuidle_device dev; + bool restore_arm_core; +}; + +static DEFINE_PER_CPU(struct cpu_state, *cpu_state); + +static DEFINE_SPINLOCK(cpuidle_lock); +static bool restore_ape; /* protected by cpuidle_lock */ +static bool restore_arm; /* protected by cpuidle_lock */ +static ktime_t time_next; /* protected by cpuidle_lock */ + +static struct clock_event_device *mtu_clkevt; +static atomic_t idle_cpus_counter = ATOMIC_INIT(0); +static atomic_t master_counter = ATOMIC_INIT(0); + +struct cstate *ux500_ci_get_cstates(int *len) +{ + if (len != NULL) + (*len) = ARRAY_SIZE(cstates); + return cstates; +} + +static void restore_sequence(struct cpu_state *state, ktime_t now) +{ + spin_lock(&cpuidle_lock); + + smp_rmb(); + if (state->restore_arm_core) { + state->restore_arm_core = false; + smp_wmb(); + + context_restore_cpu_registers(); + context_varm_restore_core(); + } + + smp_rmb(); + if (restore_arm) { + + restore_arm = false; + smp_wmb(); + + /* Restore gic settings */ + context_varm_restore_common(); + } + + smp_rmb(); + if (restore_ape) { + restore_ape = false; + smp_wmb(); + + /* + * APE has been turned off. Save GPIO wake up cause before + * clearing ioforce. + */ + context_vape_restore(); + + ux500_pm_gpio_save_wake_up_status(); + + /* Restore IO ring */ + ux500_pm_prcmu_set_ioforce(false); + + ux500_ci_dbg_console_handle_ape_resume(); + + ux500_rtcrtt_off(); + + /* + * If we're returning from ApSleep and the RTC timer + * caused the wake up, program the MTU to trigger. + */ + if ((ktime_to_us(now) >= ktime_to_us(time_next))) + time_next = ktime_add(now, ktime_set(0, 1000)); + /* Make sure have an MTU interrupt waiting for us */ + WARN_ON(clockevents_program_event(mtu_clkevt, + time_next, + now)); + } + + spin_unlock(&cpuidle_lock); + +} + +/** + * get_remaining_sleep_time() - returns remaining sleep time in + * microseconds (us) + */ +static u32 get_remaining_sleep_time(ktime_t *next, int *on_cpu) +{ + ktime_t now, t; + int cpu; + int delta; + u32 remaining_sleep_time = UINT_MAX; + + now = ktime_get(); + + /* Check next schedule to expire considering both cpus */ + + spin_lock(&cpuidle_lock); + for_each_online_cpu(cpu) { + t = per_cpu(cpu_state, cpu)->sched_wake_up; + + delta = ktime_to_us(ktime_sub(t, now)); + if ((delta < remaining_sleep_time) && (delta > 0)) { + remaining_sleep_time = (u32)delta; + if (next) + (*next) = t; + if (on_cpu) + (*on_cpu) = cpu; + } + } + spin_unlock(&cpuidle_lock); + + return remaining_sleep_time; +} + +static bool is_last_cpu_running(void) +{ + smp_rmb(); + return atomic_read(&idle_cpus_counter) == num_online_cpus(); +} + +static int determine_sleep_state(u32 *sleep_time) +{ + int i; + + int cpu; + int max_depth; + bool power_state_req; + + /* If first cpu to sleep, go to most shallow sleep state */ + if (!is_last_cpu_running()) + return CI_WFI; + + /* If other CPU is going to WFI, but not yet there wait. */ + while (1) { + if (ux500_pm_other_cpu_wfi()) + break; + + if (ux500_pm_gic_pending_interrupt()) + return -1; + + if (!is_last_cpu_running()) + return CI_WFI; + } + + power_state_req = power_state_active_is_enabled() || + prcmu_is_ac_wake_requested(); + + (*sleep_time) = get_remaining_sleep_time(NULL, NULL); + + if ((*sleep_time) == UINT_MAX) + return CI_WFI; + /* + * Never go deeper than the governor recommends even though it might be + * possible from a scheduled wake up point of view + */ + max_depth = ux500_ci_dbg_deepest_state(); + + for_each_online_cpu(cpu) { + if (max_depth > per_cpu(cpu_state, cpu)->gov_cstate) + max_depth = per_cpu(cpu_state, cpu)->gov_cstate; + } + + for (i = max_depth; i > 0; i--) { + + if ((*sleep_time) <= cstates[i].threshold) + continue; + + if (cstates[i].APE == APE_OFF) { + /* This state says APE should be off */ + if (power_state_req || + ux500_ci_dbg_force_ape_on()) + continue; + } + + /* OK state */ + break; + } + + ux500_ci_dbg_register_reason(i, power_state_req, + (*sleep_time), + max_depth); + return max(CI_WFI, i); +} + +static int enter_sleep(struct cpuidle_device *dev, + struct cpuidle_state *ci_state) +{ + ktime_t time_enter, time_exit, time_wake; + ktime_t wake_up; + int sleep_time = 0; + s64 diff; + int ret; + int target; + struct cpu_state *state; + bool slept_well = false; + int this_cpu = smp_processor_id(); + bool migrate_timer; + bool master = false; + + local_irq_disable(); + + time_enter = ktime_get(); /* Time now */ + + state = per_cpu(cpu_state, smp_processor_id()); + + wake_up = ktime_add(time_enter, tick_nohz_get_sleep_length()); + + spin_lock(&cpuidle_lock); + + /* Save scheduled wake up for this cpu */ + state->sched_wake_up = wake_up; + + /* Retrive the cstate that the governor recommends for this CPU */ + state->gov_cstate = (int) cpuidle_get_statedata(ci_state); + + if (state->gov_cstate > ux500_ci_dbg_deepest_state()) + state->gov_cstate = ux500_ci_dbg_deepest_state(); + + if (cstates[state->gov_cstate].ARM != ARM_ON) + migrate_timer = true; + else + migrate_timer = false; + + spin_unlock(&cpuidle_lock); + + atomic_inc(&idle_cpus_counter); + + /* + * Determine sleep state considering both CPUs and + * shared resources like e.g. VAPE + */ + target = determine_sleep_state(&sleep_time); + + if (target < 0) + /* "target" will be last_state in the cpuidle framework */ + goto exit_fast; + + /* Only one CPU should master the sleeping sequence */ + if (cstates[target].ARM != ARM_ON) { + smp_mb(); + if (atomic_inc_return(&master_counter) == 1) + master = true; + else + atomic_dec(&master_counter); + smp_mb(); + } + + if (migrate_timer) + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, + &this_cpu); + + + if (master && (cstates[target].ARM != ARM_ON)) { + + ux500_pm_gic_decouple(); + + /* + * Check if sleep state has changed after GIC has been frozen + */ + if (target != determine_sleep_state(&sleep_time)) { + atomic_dec(&master_counter); + goto exit; + } + + /* Copy GIC interrupt settings to PRCMU interrupt settings */ + ux500_pm_prcmu_copy_gic_settings(); + + if (ux500_pm_gic_pending_interrupt()) { + /* An interrupt found => abort */ + atomic_dec(&master_counter); + goto exit; + } + + if (ux500_pm_prcmu_pending_interrupt()) { + /* An interrupt found => abort */ + atomic_dec(&master_counter); + goto exit; + + } + /* + * No PRCMU interrupt was pending => continue the + * sleeping stages + */ + } + + if (master && (cstates[target].APE == APE_OFF)) { + ktime_t est_wake_time; + int wake_cpu; + + /* We are going to sleep or deep sleep => prepare for it */ + + /* Program the only timer that is available when APE is off */ + + sleep_time = get_remaining_sleep_time(&est_wake_time, + &wake_cpu); + + if (sleep_time == UINT_MAX) { + atomic_dec(&master_counter); + goto exit; + } + + if (cstates[target].UL_PLL == UL_PLL_OFF) + /* Compensate for ULPLL start up time */ + sleep_time -= UL_PLL_START_UP_LATENCY; + + /* + * Not checking for negative sleep time since + * determine_sleep_state has already checked that + * there is enough time. + */ + + /* Adjust for exit latency */ + sleep_time -= MIN_SLEEP_WAKE_UP_LATENCY; + + ux500_rtcrtt_next(sleep_time); + + /* + * Make sure the cpu that is scheduled first gets + * the prcmu interrupt. + */ + irq_set_affinity(IRQ_DB8500_PRCMU1, cpumask_of(wake_cpu)); + + context_vape_save(); + + ux500_ci_dbg_console_handle_ape_suspend(); + ux500_pm_prcmu_set_ioforce(true); + + spin_lock(&cpuidle_lock); + restore_ape = true; + time_next = est_wake_time; + spin_unlock(&cpuidle_lock); + } + + if (master && (cstates[target].ARM == ARM_OFF)) { + int cpu; + + context_varm_save_common(); + + spin_lock(&cpuidle_lock); + restore_arm = true; + for_each_possible_cpu(cpu) { + (per_cpu(cpu_state, cpu))->restore_arm_core = true; + } + spin_unlock(&cpuidle_lock); + } + + if (cstates[state->gov_cstate].ARM == ARM_OFF) { + context_varm_save_core(); + + if (master && (cstates[target].ARM == ARM_OFF)) + context_gic_dist_disable_unneeded_irqs(); + + context_save_cpu_registers(); + + /* + * Due to we have only 100us between requesting a + * powerstate and wfi, we clean the cache before as + * well to assure the final cache clean before wfi + * has as little as possible to do. + */ + context_clean_l1_cache_all(); + } + + ux500_ci_dbg_log(target, time_enter); + + if (master && cstates[target].ARM != ARM_ON) + prcmu_set_power_state(cstates[target].pwrst, + cstates[target].UL_PLL, + /* Is actually the AP PLL */ + cstates[target].UL_PLL); + + if (master) + atomic_dec(&master_counter); + + /* + * If deepsleep/deepidle, Save return address to SRAM and set + * this CPU in WFI. This is last core to enter sleep, so we need to + * clean both L2 and L1 caches + */ + if (cstates[state->gov_cstate].ARM == ARM_OFF) + context_save_to_sram_and_wfi(cstates[target].ARM == ARM_OFF); + else + __asm__ __volatile__ + ("dsb\n\t" "wfi\n\t" : : : "memory"); + + if (is_last_cpu_running()) + ux500_ci_dbg_wake_latency(target, sleep_time); + + time_wake = ktime_get(); + + slept_well = true; + + restore_sequence(state, time_wake); + +exit: + if (!slept_well) + /* Recouple GIC with the interrupt bus */ + ux500_pm_gic_recouple(); + + /* Use the ARM local timer for this cpu */ + if (migrate_timer) + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, + &this_cpu); +exit_fast: + + atomic_dec(&idle_cpus_counter); + + if (target < 0) + target = CI_RUNNING; + + /* 16 minutes ahead */ + wake_up = ktime_add_us(time_enter, + 1000000000); + + spin_lock(&cpuidle_lock); + /* Remove wake up time i.e. set wake up far ahead */ + state->sched_wake_up = wake_up; + spin_unlock(&cpuidle_lock); + + /* + * We might have chosen another state than what the + * governor recommended + */ + if (target != state->gov_cstate) + /* Update last state pointer used by CPUIDLE subsystem */ + dev->last_state = &(dev->states[target]); + + time_exit = ktime_get(); + diff = ktime_to_us(ktime_sub(time_exit, time_enter)); + if (diff > INT_MAX) + diff = INT_MAX; + + ret = (int)diff; + + ux500_ci_dbg_console_check_uart(); + if (slept_well) + ux500_ci_dbg_exit_latency(target, + time_exit, /* now */ + time_wake, /* exit from wfi */ + time_enter); /* enter cpuidle */ + + ux500_ci_dbg_log(CI_RUNNING, time_exit); + + local_irq_enable(); + + ux500_ci_dbg_console(); + + return ret; +} + +static int init_cstates(int cpu, struct cpu_state *state) +{ + int i; + struct cpuidle_state *ci_state; + struct cpuidle_device *dev; + + dev = &state->dev; + dev->cpu = cpu; + + for (i = 0; i < ARRAY_SIZE(cstates); i++) { + + ci_state = &dev->states[i]; + + cpuidle_set_statedata(ci_state, (void *)i); + + ci_state->exit_latency = cstates[i].exit_latency; + ci_state->target_residency = cstates[i].threshold; + ci_state->flags = cstates[i].flags; + ci_state->enter = enter_sleep; + ci_state->power_usage = cstates[i].power_usage; + snprintf(ci_state->name, CPUIDLE_NAME_LEN, "C%d", i); + strncpy(ci_state->desc, cstates[i].desc, CPUIDLE_DESC_LEN); + } + + dev->state_count = ARRAY_SIZE(cstates); + + dev->safe_state = &dev->states[0]; /* Currently not used */ + + return cpuidle_register_device(dev); +} + +struct cpuidle_driver cpuidle_drv = { + .name = "cpuidle_driver", + .owner = THIS_MODULE, +}; + +static int __init cpuidle_driver_init(void) +{ + int res = -ENODEV; + int cpu; + + if (ux500_is_svp()) + goto out; + + /* Configure wake up reasons */ + prcmu_enable_wakeups(PRCMU_WAKEUP(ARM) | PRCMU_WAKEUP(RTC) | + PRCMU_WAKEUP(ABB)); + + ux500_ci_dbg_init(); + + for_each_possible_cpu(cpu) + per_cpu(cpu_state, cpu) = kzalloc(sizeof(struct cpu_state), + GFP_KERNEL); + + res = cpuidle_register_driver(&cpuidle_drv); + if (res) + goto out; + + for_each_possible_cpu(cpu) { + res = init_cstates(cpu, per_cpu(cpu_state, cpu)); + if (res) + goto out; + pr_info("cpuidle: initiated for CPU%d.\n", cpu); + } + mtu_clkevt = nmdk_clkevt_get(); + if (!mtu_clkevt) { + pr_err("cpuidle: Could not get MTU timer.\n"); + goto out; + } + + return 0; +out: + pr_err("cpuidle: initialization failed.\n"); + return res; +} + +static void __exit cpuidle_driver_exit(void) +{ + int cpu; + struct cpuidle_device *dev; + + ux500_ci_dbg_remove(); + + for_each_possible_cpu(cpu) { + dev = &per_cpu(cpu_state, cpu)->dev; + cpuidle_unregister_device(dev); + } + + for_each_possible_cpu(cpu) + kfree(per_cpu(cpu_state, cpu)); + + cpuidle_unregister_driver(&cpuidle_drv); +} + +late_initcall(cpuidle_driver_init); +module_exit(cpuidle_driver_exit); + +MODULE_DESCRIPTION("U8500 cpuidle driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Rickard Andersson <rickard.andersson@stericsson.com>"); diff --git a/drivers/cpuidle/cpuidle-dbx500.h b/drivers/cpuidle/cpuidle-dbx500.h new file mode 100644 index 00000000000..265cce7de59 --- /dev/null +++ b/drivers/cpuidle/cpuidle-dbx500.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Rickard Andersson <rickard.andersson@stericsson.com> for + * ST-Ericsson. Loosly based on cpuidle.c by Sundar Iyer. + * License terms: GNU General Public License (GPL) version 2 + * + */ + +#ifndef __CPUIDLE_H +#define __CPUIDLE_H + +#include <linux/cpuidle.h> + +enum ARM { + ARM_OFF, + ARM_RET, + ARM_ON +}; + +enum APE { + APE_OFF, + APE_ON +}; + +enum UL_PLL { + UL_PLL_OFF, + UL_PLL_ON +}; + +enum ESRAM { + ESRAM_OFF, + ESRAM_RET +}; + +enum ci_pwrst { + CI_RUNNING = 0, /* Must be the same number as entry in cstates */ + CI_WFI = 1, /* Must be the same number as entry in cstates */ + CI_IDLE, + CI_SLEEP, + CI_DEEP_IDLE, + CI_DEEP_SLEEP, +}; + +struct cstate { + /* Required state of different hardwares */ + enum ARM ARM; + enum APE APE; + enum UL_PLL UL_PLL; + /* ESRAM = ESRAM_RET means that ESRAM context to be kept */ + enum ESRAM ESRAM; + + u32 enter_latency; + u32 exit_latency; + u32 power_usage; + u32 threshold; + u32 flags; + u8 pwrst; + + /* Only used for debugging purpose */ + enum ci_pwrst state; + char desc[CPUIDLE_DESC_LEN]; +}; + +struct cstate *ux500_ci_get_cstates(int *len); + +#endif diff --git a/drivers/cpuidle/cpuidle-dbx500_dbg.c b/drivers/cpuidle/cpuidle-dbx500_dbg.c new file mode 100644 index 00000000000..43cdcfdd241 --- /dev/null +++ b/drivers/cpuidle/cpuidle-dbx500_dbg.c @@ -0,0 +1,949 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * Author: Rickard Andersson <rickard.andersson@stericsson.com>, + * Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + */ + +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/uaccess.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/amba/serial.h> + +#include <mach/pm.h> +#include <mach/pm-timer.h> +#include <mach/gpio.h> + +#include <asm/hardware/gic.h> + +#include "cpuidle-dbx500.h" + +#define APE_ON_TIMER_INTERVAL 5 /* Seconds */ + +#define UART_RX_GPIO_PIN_MASK (1 << (CONFIG_UX500_CONSOLE_UART_GPIO_PIN % 32)) + +#define UART011_MIS_RTIS (1 << 6) /* receive timeout interrupt status */ +#define UART011_MIS_RXIS (1 << 4) /* receive interrupt status */ +#define UART011_MIS 0x40 /* Masked interrupt status register */ + +enum latency_type { + LATENCY_ENTER = 0, + LATENCY_EXIT, + LATENCY_WAKE, + NUM_LATENCY, +}; + +struct state_history_state { + u32 counter; + ktime_t time; + u32 hit_rate; + u32 state_ok; + u32 state_error; + u32 prcmu_int; + u32 pending_int; + + u32 latency_count[NUM_LATENCY]; + ktime_t latency_sum[NUM_LATENCY]; + ktime_t latency_min[NUM_LATENCY]; + ktime_t latency_max[NUM_LATENCY]; +}; + +struct state_history { + ktime_t start; + u32 state; + u32 exit_counter; + ktime_t measure_begin; + int ape_blocked; + int time_blocked; + int both_blocked; + int gov_blocked; + struct state_history_state *states; +}; +static DEFINE_PER_CPU(struct state_history, *state_history); + +static struct delayed_work cpuidle_work; +static u32 dbg_console_enable = 1; +static void __iomem *uart_base; +static struct clk *uart_clk; + +/* Blocks ApSleep and ApDeepSleep */ +static bool force_APE_on; +static bool reset_timer; +static int deepest_allowed_state = CONFIG_U8500_CPUIDLE_DEEPEST_STATE; +static u32 measure_latency; +static bool wake_latency; +static int verbose; + +static bool apidle_both_blocked; +static bool apidle_ape_blocked; +static bool apidle_time_blocked; +static bool apidle_gov_blocked; + +static struct cstate *cstates; +static int cstates_len; +static DEFINE_SPINLOCK(dbg_lock); + +bool ux500_ci_dbg_force_ape_on(void) +{ + clk_enable(uart_clk); + if (readw(uart_base + UART01x_FR) & UART01x_FR_BUSY) { + clk_disable(uart_clk); + return true; + } + clk_disable(uart_clk); + + return force_APE_on; +} + +int ux500_ci_dbg_deepest_state(void) +{ + return deepest_allowed_state; +} + +void ux500_ci_dbg_console_handle_ape_suspend(void) +{ + if (!dbg_console_enable) + return; + + enable_irq_wake(GPIO_TO_IRQ(CONFIG_UX500_CONSOLE_UART_GPIO_PIN)); +} + +void ux500_ci_dbg_console_handle_ape_resume(void) +{ + unsigned long flags; + u32 WKS_reg_value; + + if (!dbg_console_enable) + return; + + WKS_reg_value = ux500_pm_gpio_read_wake_up_status(0); + + if (WKS_reg_value & UART_RX_GPIO_PIN_MASK) { + spin_lock_irqsave(&dbg_lock, flags); + reset_timer = true; + spin_unlock_irqrestore(&dbg_lock, flags); + } + disable_irq_wake(GPIO_TO_IRQ(CONFIG_UX500_CONSOLE_UART_GPIO_PIN)); + +} + +void ux500_ci_dbg_console_check_uart(void) +{ + unsigned long flags; + u32 status; + + if (!dbg_console_enable) + return; + + clk_enable(uart_clk); + spin_lock_irqsave(&dbg_lock, flags); + status = readw(uart_base + UART011_MIS); + + if (status & (UART011_MIS_RTIS | UART011_MIS_RXIS)) + reset_timer = true; + + spin_unlock_irqrestore(&dbg_lock, flags); + clk_disable(uart_clk); +} + +void ux500_ci_dbg_console(void) +{ + unsigned long flags; + + if (!dbg_console_enable) + return; + + spin_lock_irqsave(&dbg_lock, flags); + if (reset_timer) { + reset_timer = false; + spin_unlock_irqrestore(&dbg_lock, flags); + + cancel_delayed_work(&cpuidle_work); + force_APE_on = true; + schedule_delayed_work(&cpuidle_work, + msecs_to_jiffies(APE_ON_TIMER_INTERVAL * + 1000)); + } else { + spin_unlock_irqrestore(&dbg_lock, flags); + } +} + +static void dbg_cpuidle_work_function(struct work_struct *work) +{ + force_APE_on = false; +} + +static void store_latency(struct state_history *sh, + int ctarget, + enum latency_type type, + ktime_t d, + bool lock) +{ + unsigned long flags = 0; + + if (lock) + spin_lock_irqsave(&dbg_lock, flags); + + sh->states[ctarget].latency_count[type]++; + + sh->states[ctarget].latency_sum[type] = + ktime_add(sh->states[ctarget].latency_sum[type], d); + + if (ktime_to_us(d) > ktime_to_us(sh->states[ctarget].latency_max[type])) + sh->states[ctarget].latency_max[type] = d; + + if (ktime_to_us(d) < ktime_to_us(sh->states[ctarget].latency_min[type])) + sh->states[ctarget].latency_min[type] = d; + + if (lock) + spin_unlock_irqrestore(&dbg_lock, flags); +} + +void ux500_ci_dbg_exit_latency(int ctarget, ktime_t now, ktime_t exit, + ktime_t enter) +{ + struct state_history *sh; + bool hit = true; + enum prcmu_idle_stat prcmu_status; + unsigned int d; + + if (!verbose) + return; + + sh = per_cpu(state_history, smp_processor_id()); + + sh->exit_counter++; + + d = ktime_to_us(ktime_sub(now, enter)); + + if ((ctarget + 1) < deepest_allowed_state) + hit = d < cstates[ctarget + 1].threshold; + if (d < cstates[ctarget].threshold) + hit = false; + + if (hit) + sh->states[ctarget].hit_rate++; + + if (cstates[ctarget].state < CI_IDLE) + return; + + prcmu_status = ux500_pm_prcmu_idle_stat(); + + switch (prcmu_status) { + + case DEEP_SLEEP_OK: + if (cstates[ctarget].state == CI_DEEP_SLEEP) + sh->states[ctarget].state_ok++; + break; + case SLEEP_OK: + if (cstates[ctarget].state == CI_SLEEP) + sh->states[ctarget].state_ok++; + break; + case IDLE_OK: + if (cstates[ctarget].state == CI_IDLE) + sh->states[ctarget].state_ok++; + break; + case DEEPIDLE_OK: + if (cstates[ctarget].state == CI_DEEP_IDLE) + sh->states[ctarget].state_ok++; + break; + case PRCMU2ARMPENDINGIT_ER: + sh->states[ctarget].prcmu_int++; + break; + case ARMPENDINGIT_ER: + sh->states[ctarget].pending_int++; + break; + default: + pr_info("cpuidle: unknown prcmu exit code: 0x%x state: %d\n", + prcmu_status, cstates[ctarget].state); + sh->states[ctarget].state_error++; + break; + } + + if (!measure_latency) + return; + + store_latency(sh, + ctarget, + LATENCY_EXIT, + ktime_sub(now, exit), + true); +} + +void ux500_ci_dbg_wake_latency(int ctarget, int sleep_time) +{ + struct state_history *sh; + ktime_t l; + ktime_t zero_time; + + if (!wake_latency || cstates[ctarget].state < CI_IDLE) + return; + + l = zero_time = ktime_set(0, 0); + sh = per_cpu(state_history, smp_processor_id()); + + if (cstates[ctarget].state >= CI_SLEEP) + l = u8500_rtc_exit_latency_get(); + + if (cstates[ctarget].state == CI_IDLE) { + ktime_t d = ktime_set(0, sleep_time * 1000); + ktime_t now = ktime_get(); + + d = ktime_add(d, sh->start); + if (ktime_to_us(now) > ktime_to_us(d)) + l = ktime_sub(now, d); + else + l = zero_time; + } + + if (!ktime_equal(zero_time, l)) + store_latency(sh, + ctarget, + LATENCY_WAKE, + l, + true); +} + +static void state_record_time(struct state_history *sh, int ctarget, + ktime_t now, ktime_t start, bool latency) +{ + ktime_t dtime; + + dtime = ktime_sub(now, sh->start); + sh->states[sh->state].time = ktime_add(sh->states[sh->state].time, + dtime); + + sh->start = now; + sh->state = ctarget; + + if (latency && cstates[ctarget].state != CI_RUNNING && measure_latency) + store_latency(sh, + ctarget, + LATENCY_ENTER, + ktime_sub(now, start), + false); + + sh->states[sh->state].counter++; +} + +void ux500_ci_dbg_register_reason(int idx, bool power_state_req, + u32 time, u32 max_depth) +{ + if (cstates[idx].state == CI_IDLE && verbose) { + apidle_ape_blocked = power_state_req; + apidle_time_blocked = time < cstates[idx + 1].threshold; + apidle_both_blocked = power_state_req && apidle_time_blocked; + apidle_gov_blocked = cstates[max_depth].state == CI_IDLE; + } +} + +void ux500_ci_dbg_log(int ctarget, ktime_t enter_time) +{ + int i; + ktime_t now; + unsigned long flags; + struct state_history *sh; + struct state_history *sh_other; + int this_cpu; + + this_cpu = smp_processor_id(); + + now = ktime_get(); + + sh = per_cpu(state_history, this_cpu); + + spin_lock_irqsave(&dbg_lock, flags); + + if (cstates[ctarget].state == CI_IDLE && verbose) { + if (apidle_both_blocked) + sh->both_blocked++; + if (apidle_ape_blocked) + sh->ape_blocked++; + if (apidle_time_blocked) + sh->time_blocked++; + if (apidle_gov_blocked) + sh->gov_blocked++; + } + + /* + * Check if current state is just a repeat of + * the state we're already in, then just quit. + */ + if (ctarget == sh->state) + goto done; + + state_record_time(sh, ctarget, now, enter_time, true); + + /* + * Update other cpus, (this_cpu = A, other cpus = B) if: + * - A = running and B != WFI | running: Set B to WFI + * - A = WFI and then B must be running: No changes + * - A = !WFI && !RUNNING and then B must be WFI: B sets to A + */ + + if (sh->state == CI_WFI) + goto done; + + for_each_possible_cpu(i) { + + if (this_cpu == i) + continue; + + sh_other = per_cpu(state_history, i); + + /* Same state, continue */ + if (sh_other->state == sh->state) + continue; + + if (cstates[ctarget].state == CI_RUNNING && + cstates[sh_other->state].state != CI_WFI) { + state_record_time(sh_other, CI_WFI, now, + enter_time, false); + continue; + } + /* + * This cpu is something else than running or wfi, both must be + * in the same state. + */ + state_record_time(sh_other, ctarget, now, enter_time, true); + } +done: + spin_unlock_irqrestore(&dbg_lock, flags); +} + +static void state_history_reset(void) +{ + unsigned long flags; + unsigned int cpu; + int i, j; + struct state_history *sh; + + spin_lock_irqsave(&dbg_lock, flags); + + for_each_possible_cpu(cpu) { + sh = per_cpu(state_history, cpu); + for (i = 0; i < cstates_len; i++) { + sh->states[i].counter = 0; + sh->states[i].hit_rate = 0; + sh->states[i].state_ok = 0; + sh->states[i].state_error = 0; + sh->states[i].prcmu_int = 0; + sh->states[i].pending_int = 0; + + sh->states[i].time = ktime_set(0, 0); + + for (j = 0; j < NUM_LATENCY; j++) { + sh->states[i].latency_count[j] = 0; + sh->states[i].latency_min[j] = ktime_set(0, + 10000000); + sh->states[i].latency_max[j] = ktime_set(0, 0); + sh->states[i].latency_sum[j] = ktime_set(0, 0); + } + } + + sh->start = ktime_get(); + sh->measure_begin = sh->start; + /* Don't touch sh->state, since that is where we are now */ + + sh->exit_counter = 0; + sh->ape_blocked = 0; + sh->time_blocked = 0; + sh->both_blocked = 0; + sh->gov_blocked = 0; + } + spin_unlock_irqrestore(&dbg_lock, flags); +} + +static int get_val(const char __user *user_buf, + size_t count, int min, int max) +{ + long unsigned val; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &val); + + if (err) + return err; + + if (val > max) + val = max; + if (val < min) + val = min; + + return val; +} + +static ssize_t set_deepest_state(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int val; + + val = get_val(user_buf, count, CI_WFI, cstates_len - 1); + + if (val < 0) + return val; + + deepest_allowed_state = val; + + pr_debug("cpuidle: changed deepest allowed sleep state to %d.\n", + deepest_allowed_state); + + return count; +} + +static int deepest_state_print(struct seq_file *s, void *p) +{ + seq_printf(s, "Deepest allowed sleep state is %d\n", + deepest_allowed_state); + + return 0; +} + +static ssize_t stats_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + state_history_reset(); + return count; +} + +static int wake_latency_read(struct seq_file *s, void *p) +{ + seq_printf(s, "wake latency measurements is %s\n", + wake_latency ? "on" : "off"); + return 0; +} + +static ssize_t wake_latency_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int val = get_val(user_buf, count, 0, 1); + if (val < 0) + return val; + + wake_latency = val; + ux500_rtcrtt_measure_latency(wake_latency); + return count; +} + +static int verbose_read(struct seq_file *s, void *p) +{ + seq_printf(s, "verbose debug is %s\n", verbose ? "on" : "off"); + return 0; +} + +static ssize_t verbose_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int val = get_val(user_buf, count, 0, 1); + if (val < 0) + return val; + + verbose = val; + state_history_reset(); + + return count; +} + +static void stats_disp_one(struct seq_file *s, struct state_history *sh, + s64 total_us, int i) +{ + int j; + s64 avg[NUM_LATENCY]; + s64 t_us; + s64 perc; + ktime_t init_time, zero_time; + + init_time = ktime_set(0, 10000000); + zero_time = ktime_set(0, 0); + + memset(&avg, 0, sizeof(s64) * NUM_LATENCY); + + for (j = 0; j < NUM_LATENCY; j++) + avg[j] = ktime_to_us(sh->states[i].latency_sum[j]); + + t_us = ktime_to_us(sh->states[i].time); + perc = ktime_to_us(sh->states[i].time); + do_div(t_us, 1000); /* to ms */ + do_div(total_us, 100); + if (total_us) + do_div(perc, total_us); + + for (j = 0; j < NUM_LATENCY; j++) { + if (sh->states[i].latency_count[j]) + do_div(avg[j], sh->states[i].latency_count[j]); + } + + seq_printf(s, "\n%d - %s: %u", + i, cstates[i].desc, + sh->states[i].counter); + + if (sh->states[i].counter == 0) + return; + + if (i > CI_WFI && verbose) + seq_printf(s, " (%u prcmu_int:%u int:%u err:%u)", + sh->states[i].state_ok, + sh->states[i].prcmu_int, + sh->states[i].pending_int, + sh->states[i].state_error); + + seq_printf(s, " in %d ms %d%%", + (u32) t_us, (u32)perc); + + if (cstates[i].state == CI_IDLE && verbose) + seq_printf(s, ", reg:%d time:%d both:%d gov:%d", + sh->ape_blocked, sh->time_blocked, + sh->both_blocked, sh->gov_blocked); + + if (sh->states[i].counter && verbose) + seq_printf(s, ", hit rate: %u%% ", + 100 * sh->states[i].hit_rate / + sh->states[i].counter); + + if (i == CI_RUNNING || !(measure_latency || wake_latency)) + return; + + for (j = 0; j < NUM_LATENCY; j++) { + bool show = false; + if (!ktime_equal(sh->states[i].latency_min[j], init_time)) { + seq_printf(s, "\n\t\t\t\t"); + switch (j) { + case LATENCY_ENTER: + if (measure_latency) { + seq_printf(s, "enter: "); + show = true; + } + break; + case LATENCY_EXIT: + if (measure_latency) { + seq_printf(s, "exit: "); + show = true; + } + break; + case LATENCY_WAKE: + if (wake_latency) { + seq_printf(s, "wake: "); + show = true; + } + break; + default: + seq_printf(s, "unknown!: "); + break; + } + + if (!show) + continue; + + if (ktime_equal(sh->states[i].latency_min[j], + zero_time)) + seq_printf(s, "min < 30"); + else + seq_printf(s, "min %lld", + ktime_to_us(sh->states[i].latency_min[j])); + + seq_printf(s, " avg %lld max %lld us, count: %d", + avg[j], + ktime_to_us(sh->states[i].latency_max[j]), + sh->states[i].latency_count[j]); + } + } +} + +static int stats_print(struct seq_file *s, void *p) +{ + int cpu; + int i; + unsigned long flags; + struct state_history *sh; + ktime_t total, wall; + s64 total_us, total_s; + + for_each_online_cpu(cpu) { + sh = per_cpu(state_history, cpu); + spin_lock_irqsave(&dbg_lock, flags); + seq_printf(s, "\nCPU%d\n", cpu); + + total = ktime_set(0, 0); + + for (i = 0; i < cstates_len; i++) + total = ktime_add(total, sh->states[i].time); + + wall = ktime_sub(ktime_get(), sh->measure_begin); + + total_us = ktime_to_us(wall); + total_s = ktime_to_ms(wall); + + do_div(total_s, 1000); + + if (verbose) { + if (total_s) + seq_printf(s, + "wake ups per s: %u.%u \n", + sh->exit_counter / (int) total_s, + (10 * sh->exit_counter / (int) total_s) - + 10 * (sh->exit_counter / (int) total_s)); + + seq_printf(s, + "\ndelta accounted vs wall clock: %lld us\n", + ktime_to_us(ktime_sub(wall, total))); + } + + for (i = 0; i < cstates_len; i++) + stats_disp_one(s, sh, total_us, i); + + seq_printf(s, "\n"); + spin_unlock_irqrestore(&dbg_lock, flags); + } + seq_printf(s, "\n"); + return 0; +} + + +static int ap_family_show(struct seq_file *s, void *iter) +{ + int i; + u32 count = 0; + unsigned long flags; + struct state_history *sh; + + sh = per_cpu(state_history, 0); + spin_lock_irqsave(&dbg_lock, flags); + + for (i = 0 ; i < cstates_len; i++) { + if (cstates[i].state == (enum ci_pwrst)s->private) + count += sh->states[i].counter; + } + + seq_printf(s, "%u\n", count); + spin_unlock_irqrestore(&dbg_lock, flags); + + return 0; +} + +static int deepest_state_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, deepest_state_print, inode->i_private); +} + +static int verbose_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, verbose_read, inode->i_private); +} + +static int stats_open_file(struct inode *inode, struct file *file) +{ + return single_open(file, stats_print, inode->i_private); +} + +static int ap_family_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ap_family_show, inode->i_private); +} + +static int wake_latency_open(struct inode *inode, + struct file *file) +{ + return single_open(file, wake_latency_read, inode->i_private); +} + +static const struct file_operations deepest_state_fops = { + .open = deepest_state_open_file, + .write = set_deepest_state, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations verbose_state_fops = { + .open = verbose_open_file, + .write = verbose_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations stats_fops = { + .open = stats_open_file, + .write = stats_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ap_family_fops = { + .open = ap_family_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations wake_latency_fops = { + .open = wake_latency_open, + .write = wake_latency_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static struct dentry *cpuidle_dir; + +static void __init setup_debugfs(void) +{ + cpuidle_dir = debugfs_create_dir("cpuidle", NULL); + if (IS_ERR_OR_NULL(cpuidle_dir)) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("deepest_state", + S_IWUGO | S_IRUGO, cpuidle_dir, + NULL, &deepest_state_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("verbose", + S_IWUGO | S_IRUGO, cpuidle_dir, + NULL, &verbose_state_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("stats", + S_IRUGO, cpuidle_dir, NULL, + &stats_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_bool("dbg_console_enable", + S_IWUGO | S_IRUGO, cpuidle_dir, + &dbg_console_enable))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_bool("measure_latency", + S_IWUGO | S_IRUGO, cpuidle_dir, + &measure_latency))) + goto fail; + + + if (IS_ERR_OR_NULL(debugfs_create_file("wake_latency", + S_IWUGO | S_IRUGO, cpuidle_dir, + NULL, + &wake_latency_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("ap_idle", S_IRUGO, + cpuidle_dir, + (void *)CI_IDLE, + &ap_family_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("ap_sleep", S_IRUGO, + cpuidle_dir, + (void *)CI_SLEEP, + &ap_family_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("ap_deepidle", S_IRUGO, + cpuidle_dir, + (void *)CI_DEEP_IDLE, + &ap_family_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("ap_deepsleep", S_IRUGO, + cpuidle_dir, + (void *)CI_DEEP_SLEEP, + &ap_family_fops))) + goto fail; + + return; +fail: + debugfs_remove_recursive(cpuidle_dir); +} + +#define __UART_BASE(soc, x) soc##_UART##x##_BASE +#define UART_BASE(soc, x) __UART_BASE(soc, x) + +void __init ux500_ci_dbg_init(void) +{ + static const char clkname[] __initconst + = "uart" __stringify(CONFIG_UX500_DEBUG_UART); + unsigned long baseaddr; + int cpu; + + struct state_history *sh; + + cstates = ux500_ci_get_cstates(&cstates_len); + + if (deepest_allowed_state > cstates_len) + deepest_allowed_state = cstates_len; + + for_each_possible_cpu(cpu) { + per_cpu(state_history, cpu) = kzalloc(sizeof(struct state_history), + GFP_KERNEL); + sh = per_cpu(state_history, cpu); + sh->states = kzalloc(sizeof(struct state_history_state) + * cstates_len, + GFP_KERNEL); + } + + state_history_reset(); + + for_each_possible_cpu(cpu) { + sh = per_cpu(state_history, cpu); + /* Only first CPU used during boot */ + if (cpu == 0) + sh->state = CI_RUNNING; + else + sh->state = CI_WFI; + } + + setup_debugfs(); + + /* Uart debug init */ + + if (cpu_is_u8500()) + baseaddr = UART_BASE(U8500, CONFIG_UX500_DEBUG_UART); + else if (cpu_is_u5500()) + baseaddr = UART_BASE(U5500, CONFIG_UX500_DEBUG_UART); + else + ux500_unknown_soc(); + + uart_base = ioremap(baseaddr, SZ_4K); + BUG_ON(!uart_base); + + uart_clk = clk_get_sys(clkname, NULL); + BUG_ON(IS_ERR(uart_clk)); + + INIT_DELAYED_WORK_DEFERRABLE(&cpuidle_work, dbg_cpuidle_work_function); + +} + +void ux500_ci_dbg_remove(void) +{ + int cpu; + struct state_history *sh; + + debugfs_remove_recursive(cpuidle_dir); + + for_each_possible_cpu(cpu) { + sh = per_cpu(state_history, cpu); + kfree(sh->states); + kfree(sh); + } + + iounmap(uart_base); +} diff --git a/drivers/cpuidle/cpuidle-dbx500_dbg.h b/drivers/cpuidle/cpuidle-dbx500_dbg.h new file mode 100644 index 00000000000..b8089c478a1 --- /dev/null +++ b/drivers/cpuidle/cpuidle-dbx500_dbg.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * Author: Rickard Andersson <rickard.andersson@stericsson.com> for ST-Ericsson + * Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + */ + +#ifndef CPUIDLE_DBG_H +#define CPUIDLE_DBG_H + +#ifdef CONFIG_UX500_CPUIDLE_DEBUG +void ux500_ci_dbg_init(void); +void ux500_ci_dbg_remove(void); + +void ux500_ci_dbg_log(int ctarget, ktime_t enter_time); +void ux500_ci_dbg_wake_latency(int ctarget, int sleep_time); +void ux500_ci_dbg_exit_latency(int ctarget, ktime_t now, ktime_t exit, + ktime_t enter); + +void ux500_ci_dbg_register_reason(int idx, bool power_state_req, + u32 sleep_time, u32 max_depth); + +bool ux500_ci_dbg_force_ape_on(void); +int ux500_ci_dbg_deepest_state(void); + +void ux500_ci_dbg_console(void); +void ux500_ci_dbg_console_check_uart(void); +void ux500_ci_dbg_console_handle_ape_resume(void); +void ux500_ci_dbg_console_handle_ape_suspend(void); + +#else + +static inline void ux500_ci_dbg_init(void) { } +static inline void ux500_ci_dbg_remove(void) { } + +static inline void ux500_ci_dbg_log(int ctarget, + ktime_t enter_time) { } + +static inline void ux500_ci_dbg_exit_latency(int ctarget, + ktime_t now, ktime_t exit, + ktime_t enter) { } +static inline void ux500_ci_dbg_wake_latency(int ctarget, int sleep_time) { } + + +static inline void ux500_ci_dbg_register_reason(int idx, bool power_state_req, + u32 sleep_time, u32 max_depth) { } + +static inline bool ux500_ci_dbg_force_ape_on(void) +{ + return false; +} + +static inline int ux500_ci_dbg_deepest_state(void) +{ + /* This means no lower sleep state than ApIdle */ + return CONFIG_U8500_CPUIDLE_DEEPEST_STATE; +} + +static inline void ux500_ci_dbg_console(void) { } +static inline void ux500_ci_dbg_console_check_uart(void) { } +static inline void ux500_ci_dbg_console_handle_ape_resume(void) { } +static inline void ux500_ci_dbg_console_handle_ape_suspend(void) { } + +#endif +#endif diff --git a/drivers/cpuidle/governors/menu.c b/drivers/cpuidle/governors/menu.c index c47f3d09c1e..ed28f774ca4 100644 --- a/drivers/cpuidle/governors/menu.c +++ b/drivers/cpuidle/governors/menu.c @@ -19,6 +19,8 @@ #include <linux/tick.h> #include <linux/sched.h> #include <linux/math64.h> +#include <linux/cpu.h> +#include <linux/sysfs.h> #define BUCKETS 12 #define INTERVALS 8 @@ -121,6 +123,8 @@ struct menu_device { int interval_ptr; }; +static int tune_multiplier = 1024; +static int forced_state; #define LOAD_INT(x) ((x) >> FSHIFT) #define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100) @@ -170,6 +174,9 @@ static inline int performance_multiplier(void) { int mult = 1; + if (tune_multiplier <= 1) + return tune_multiplier; + /* for higher loadavg, we are more reluctant */ mult += 2 * get_loadavg(); @@ -177,6 +184,9 @@ static inline int performance_multiplier(void) /* for IO wait tasks (per cpu!) we add 5x each */ mult += 10 * nr_iowait_cpu(smp_processor_id()); + if (tune_multiplier != 1024) + mult = (tune_multiplier * mult) / 1024; + return mult; } @@ -281,26 +291,34 @@ static int menu_select(struct cpuidle_device *dev) if (data->expected_us > 5) data->last_state_idx = CPUIDLE_DRIVER_STATE_START; - /* - * Find the idle state with the lowest power while satisfying - * our constraints. - */ - for (i = CPUIDLE_DRIVER_STATE_START; i < dev->state_count; i++) { - struct cpuidle_state *s = &dev->states[i]; - - if (s->flags & CPUIDLE_FLAG_IGNORE) - continue; - if (s->target_residency > data->predicted_us) - continue; - if (s->exit_latency > latency_req) - continue; - if (s->exit_latency * multiplier > data->predicted_us) - continue; - - if (s->power_usage < power_usage) { - power_usage = s->power_usage; - data->last_state_idx = i; - data->exit_us = s->exit_latency; + WARN((forced_state >= dev->state_count), \ + "Forced state value out of range.\n"); + + if ((forced_state != 0) && (forced_state < dev->state_count)) { + data->exit_us = dev->states[forced_state].exit_latency; + data->last_state_idx = forced_state; + } else { + /* + * Find the idle state with the lowest power while satisfying + * our constraints. + */ + for (i = CPUIDLE_DRIVER_STATE_START; i < dev->state_count; i++) { + struct cpuidle_state *s = &dev->states[i]; + + if (s->flags & CPUIDLE_FLAG_IGNORE) + continue; + if (s->target_residency > data->predicted_us) + continue; + if (s->exit_latency > latency_req) + continue; + if (s->exit_latency * multiplier > data->predicted_us) + continue; + + if (s->power_usage < power_usage) { + power_usage = s->power_usage; + data->last_state_idx = i; + data->exit_us = s->exit_latency; + } } } @@ -381,6 +399,63 @@ static void menu_update(struct cpuidle_device *dev) data->interval_ptr = 0; } +int cpuidle_set_multiplier(unsigned int value) +{ + + if (value > 1024) + tune_multiplier = 1024; + else + tune_multiplier = value; + + return 0; +} +EXPORT_SYMBOL(cpuidle_set_multiplier); + +/* Writing 0 will remove the forced state. */ +int cpuidle_force_state(unsigned int state) +{ + forced_state = state; + + return 0; +} +EXPORT_SYMBOL(cpuidle_force_state); + +static ssize_t show_multiplier(struct sysdev_class *class, + struct sysdev_class_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", tune_multiplier); +} + +static ssize_t store_multiplier(struct sysdev_class *class, + struct sysdev_class_attribute *attr, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + + if (ret != 1) + return -EINVAL; + + cpuidle_set_multiplier(input); + + return count; +} + + +static SYSDEV_CLASS_ATTR(multiplier, 0644, show_multiplier, store_multiplier); + +static struct attribute *dbs_attributes[] = { + &attr_multiplier.attr, + NULL +}; + +static struct attribute_group dbs_attr_group = { + .attrs = dbs_attributes, + .name = "cpuidle", +}; + /** * menu_enable_device - scans a CPU's states and does setup * @dev: the CPU @@ -408,7 +483,15 @@ static struct cpuidle_governor menu_governor = { */ static int __init init_menu(void) { - return cpuidle_register_governor(&menu_governor); + int ret; + + ret = cpuidle_register_governor(&menu_governor); + + sysfs_merge_group(&(cpu_sysdev_class.kset.kobj), + &dbs_attr_group); + + return ret; + } /** @@ -416,6 +499,9 @@ static int __init init_menu(void) */ static void __exit exit_menu(void) { + sysfs_unmerge_group(&(cpu_sysdev_class.kset.kobj), + &dbs_attr_group); + cpuidle_unregister_governor(&menu_governor); } diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index 467e4dcb20a..3608ee72557 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -6,20 +6,30 @@ * License terms: GNU General Public License (GPL) version 2 */ -#include <linux/dma-mapping.h> #include <linux/kernel.h> -#include <linux/slab.h> #include <linux/dmaengine.h> +#include <linux/dma-mapping.h> #include <linux/platform_device.h> #include <linux/clk.h> #include <linux/delay.h> +#include <linux/slab.h> +#include <linux/version.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/regulator/consumer.h> #include <linux/err.h> -#include <linux/amba/bus.h> #include <plat/ste_dma40.h> #include "ste_dma40_ll.h" +#ifdef CONFIG_STE_DMA40_DEBUG +#include "ste_dma40_debug.h" +#define MARK sted40_history_text((char *)__func__) +#else +#define MARK +#endif + #define D40_NAME "dma40" #define D40_PHY_CHAN -1 @@ -31,6 +41,9 @@ /* Maximum iterations taken before giving up suspending a channel */ #define D40_SUSPEND_MAX_IT 500 +/* Milliseconds */ +#define DMA40_AUTOSUSPEND_DELAY 100 + /* Hardware requirement on LCLA alignment */ #define LCLA_ALIGNMENT 0x40000 @@ -46,6 +59,9 @@ #define D40_ALLOC_PHY (1 << 30) #define D40_ALLOC_LOG_FREE 0 +/* Hardware designer of the block */ +#define D40_PERIPHID2_DESIGNER 0x8 + /** * enum 40_command - The different commands and/or statuses. * @@ -61,6 +77,51 @@ enum d40_command { D40_DMA_SUSPENDED = 3 }; +/* + * These are the registers that has to be saved and later restored + * when the DMA hw is powered off. + * TODO: Add save/restore of D40_DREG_GCC on dma40 v3 or later, if that works. + */ +static u32 d40_backup_regs[] = { + D40_DREG_LCPA, + D40_DREG_LCLA, + D40_DREG_PRMSE, + D40_DREG_PRMSO, + D40_DREG_PRMOE, + D40_DREG_PRMOO, +}; + +/* TODO: Check if all these registers have to be saved/restored on dma40 v3 */ +static u32 d40_backup_regs_v3[] = { + D40_DREG_PSEG1, + D40_DREG_PSEG2, + D40_DREG_PSEG3, + D40_DREG_PSEG4, + D40_DREG_PCEG1, + D40_DREG_PCEG2, + D40_DREG_PCEG3, + D40_DREG_PCEG4, + D40_DREG_RSEG1, + D40_DREG_RSEG2, + D40_DREG_RSEG3, + D40_DREG_RSEG4, + D40_DREG_RCEG1, + D40_DREG_RCEG2, + D40_DREG_RCEG3, + D40_DREG_RCEG4, +}; + +static u32 d40_backup_regs_chan[] = { + D40_CHAN_REG_SSCFG, + D40_CHAN_REG_SSELT, + D40_CHAN_REG_SSPTR, + D40_CHAN_REG_SSLNK, + D40_CHAN_REG_SDCFG, + D40_CHAN_REG_SDELT, + D40_CHAN_REG_SDPTR, + D40_CHAN_REG_SDLNK, +}; + /** * struct d40_lli_pool - Structure for keeping LLIs in memory * @@ -87,15 +148,16 @@ struct d40_lli_pool { * points into the lli_pool, to base if lli_len > 1 or to pre_alloc_lli if * lli_len equals one. * @lli_log: Same as above but for logical channels. + * @last_lcla: lcla used for last link (logical channels) * @lli_pool: The pool with two entries pre-allocated. * @lli_len: Number of llis of current descriptor. - * @lli_current: Number of transferred llis. + * @lli_current: Number of transfered llis. * @lcla_alloc: Number of LCLA entries allocated. * @txd: DMA engine struct. Used for among other things for communication * during a transfer. * @node: List entry. * @is_in_client_list: true if the client owns this descriptor. - * the previous one. + * @cyclic: true if this is a cyclic job * * This descriptor is used for both logical and physical transfers. */ @@ -104,6 +166,7 @@ struct d40_desc { struct d40_phy_lli_bidir lli_phy; /* LLI logical */ struct d40_log_lli_bidir lli_log; + struct d40_log_lli *last_lcla; struct d40_lli_pool lli_pool; int lli_len; @@ -142,6 +205,7 @@ struct d40_lcla_pool { * channels. * * @lock: A lock protection this entity. + * @reserved: True if used by secure world or otherwise. * @num: The physical channel number of this entity. * @allocated_src: Bit mapped to show which src event line's are mapped to * this physical channel. Can also be free or physically allocated. @@ -151,6 +215,7 @@ struct d40_lcla_pool { */ struct d40_phy_res { spinlock_t lock; + bool reserved; int num; u32 allocated_src; u32 allocated_dst; @@ -174,20 +239,21 @@ struct d40_base; * @tasklet: Tasklet that gets scheduled from interrupt context to complete a * transfer and call client callback. * @client: Cliented owned descriptor list. - * @pending_queue: Submitted jobs, to be issued by issue_pending() * @active: Active descriptor. + * @done: Completed jobs * @queue: Queued jobs. - * @prepare_queue: Prepared jobs. * @dma_cfg: The client configuration of this dma channel. * @configured: whether the dma_cfg configuration is valid * @base: Pointer to the device instance struct. + * @cdesc: Cyclic descriptor * @src_def_cfg: Default cfg register setting for src. * @dst_def_cfg: Default cfg register setting for dst. * @log_def: Default logical channel settings. - * @lcla: Space for one dst src pair for logical channel transfers. * @lcpa: Pointer to dst and src lcpa settings. * @runtime_addr: runtime configured address. * @runtime_direction: runtime configured direction. + * @src_dev_addr: device source address for the channel transfer. + * @dst_dev_addr: device destination address for the channel transfer. * * This struct can either "be" a logical or a physical channel. */ @@ -202,13 +268,13 @@ struct d40_chan { struct dma_chan chan; struct tasklet_struct tasklet; struct list_head client; - struct list_head pending_queue; struct list_head active; + struct list_head done; struct list_head queue; - struct list_head prepare_queue; struct stedma40_chan_cfg dma_cfg; bool configured; struct d40_base *base; + struct stedma40_cyclic_desc *cdesc; /* Default register configurations */ u32 src_def_cfg; u32 dst_def_cfg; @@ -217,6 +283,8 @@ struct d40_chan { /* Runtime reconfiguration */ dma_addr_t runtime_addr; enum dma_data_direction runtime_direction; + dma_addr_t src_dev_addr; + dma_addr_t dst_dev_addr; }; /** @@ -240,6 +308,7 @@ struct d40_chan { * @dma_both: dma_device channels that can do both memcpy and slave transfers. * @dma_slave: dma_device channels that can do only do slave transfers. * @dma_memcpy: dma_device channels that can do only do memcpy transfers. + * @phy_chans: Room for all possible physical channels in system. * @log_chans: Room for all possible logical channels in system. * @lookup_log_chans: Used to map interrupt number to logical channel. Points * to log_chans entries. @@ -247,12 +316,22 @@ struct d40_chan { * to phy_chans entries. * @plat_data: Pointer to provided platform_data which is the driver * configuration. + * @lcpa_regulator: Pointer to hold the regulator for the esram bank for lcla. * @phy_res: Vector containing all physical channels. * @lcla_pool: lcla pool settings and data. * @lcpa_base: The virtual mapped address of LCPA. * @phy_lcpa: The physical address of the LCPA. * @lcpa_size: The size of the LCPA area. * @desc_slab: cache for descriptors. + * @usage: The number of dma executions. Used by suspend to determite if + * the dma can suspend or not. + * @usage_lock: lock for usage count. + * @reg_val_backup: Here the values of some hardware registers are stored + * before the DMA is powered off. They are restored when the power is back on. + * @reg_val_backup_v3: Backup of registers that only exits on dma40 v3 and + * later. + * @reg_val_backup_chan: Backup data for standard channel parameter registers. + * @initialized: true if the dma has been initialized */ struct d40_base { spinlock_t interrupt_lock; @@ -274,6 +353,7 @@ struct d40_base { struct d40_chan **lookup_log_chans; struct d40_chan **lookup_phy_chans; struct stedma40_platform_data *plat_data; + struct regulator *lcpa_regulator; /* Physical half channels */ struct d40_phy_res *phy_res; struct d40_lcla_pool lcla_pool; @@ -281,6 +361,14 @@ struct d40_base { dma_addr_t phy_lcpa; resource_size_t lcpa_size; struct kmem_cache *desc_slab; + int usage; + spinlock_t usage_lock; + u32 reg_val_backup + [ARRAY_SIZE(d40_backup_regs)]; + u32 reg_val_backup_v3 + [ARRAY_SIZE(d40_backup_regs_v3)]; + u32 *reg_val_backup_chan; + bool initialized; }; /** @@ -338,9 +426,8 @@ static void __iomem *chan_base(struct d40_chan *chan) d40_err(chan2dev(d40c), format, ## arg) static int d40_pool_lli_alloc(struct d40_chan *d40c, struct d40_desc *d40d, - int lli_len) + int lli_len, bool is_log) { - bool is_log = chan_is_logical(d40c); u32 align; void *base; @@ -402,6 +489,7 @@ static void d40_pool_lli_free(struct d40_chan *d40c, struct d40_desc *d40d) d40d->lli_log.dst = NULL; d40d->lli_phy.src = NULL; d40d->lli_phy.dst = NULL; + d40d->last_lcla = NULL; } static int d40_lcla_alloc_one(struct d40_chan *d40c, @@ -410,19 +498,18 @@ static int d40_lcla_alloc_one(struct d40_chan *d40c, unsigned long flags; int i; int ret = -EINVAL; - int p; spin_lock_irqsave(&d40c->base->lcla_pool.lock, flags); - p = d40c->phy_chan->num * D40_LCLA_LINK_PER_EVENT_GRP; - /* * Allocate both src and dst at the same time, therefore the half * start on 1 since 0 can't be used since zero is used as end marker. */ for (i = 1 ; i < D40_LCLA_LINK_PER_EVENT_GRP / 2; i++) { - if (!d40c->base->lcla_pool.alloc_map[p + i]) { - d40c->base->lcla_pool.alloc_map[p + i] = d40d; + int idx = d40c->phy_chan->num * D40_LCLA_LINK_PER_EVENT_GRP + i; + + if (!d40c->base->lcla_pool.alloc_map[idx]) { + d40c->base->lcla_pool.alloc_map[idx] = d40d; d40d->lcla_alloc++; ret = i; break; @@ -447,10 +534,10 @@ static int d40_lcla_free_all(struct d40_chan *d40c, spin_lock_irqsave(&d40c->base->lcla_pool.lock, flags); for (i = 1 ; i < D40_LCLA_LINK_PER_EVENT_GRP / 2; i++) { - if (d40c->base->lcla_pool.alloc_map[d40c->phy_chan->num * - D40_LCLA_LINK_PER_EVENT_GRP + i] == d40d) { - d40c->base->lcla_pool.alloc_map[d40c->phy_chan->num * - D40_LCLA_LINK_PER_EVENT_GRP + i] = NULL; + int idx = d40c->phy_chan->num * D40_LCLA_LINK_PER_EVENT_GRP + i; + + if (d40c->base->lcla_pool.alloc_map[idx] == d40d) { + d40c->base->lcla_pool.alloc_map[idx] = NULL; d40d->lcla_alloc--; if (d40d->lcla_alloc == 0) { ret = 0; @@ -478,13 +565,15 @@ static struct d40_desc *d40_desc_get(struct d40_chan *d40c) struct d40_desc *d; struct d40_desc *_d; - list_for_each_entry_safe(d, _d, &d40c->client, node) + list_for_each_entry_safe(d, _d, &d40c->client, node) { if (async_tx_test_ack(&d->txd)) { + d40_pool_lli_free(d40c, d); d40_desc_remove(d); desc = d; - memset(desc, 0, sizeof(*desc)); + memset(desc, 0, sizeof(struct d40_desc)); break; } + } } if (!desc) @@ -509,128 +598,115 @@ static void d40_desc_submit(struct d40_chan *d40c, struct d40_desc *desc) list_add_tail(&desc->node, &d40c->active); } -static void d40_phy_lli_load(struct d40_chan *chan, struct d40_desc *desc) +static void d40_desc_done(struct d40_chan *d40c, struct d40_desc *desc) { - struct d40_phy_lli *lli_dst = desc->lli_phy.dst; - struct d40_phy_lli *lli_src = desc->lli_phy.src; - void __iomem *base = chan_base(chan); - - writel(lli_src->reg_cfg, base + D40_CHAN_REG_SSCFG); - writel(lli_src->reg_elt, base + D40_CHAN_REG_SSELT); - writel(lli_src->reg_ptr, base + D40_CHAN_REG_SSPTR); - writel(lli_src->reg_lnk, base + D40_CHAN_REG_SSLNK); - - writel(lli_dst->reg_cfg, base + D40_CHAN_REG_SDCFG); - writel(lli_dst->reg_elt, base + D40_CHAN_REG_SDELT); - writel(lli_dst->reg_ptr, base + D40_CHAN_REG_SDPTR); - writel(lli_dst->reg_lnk, base + D40_CHAN_REG_SDLNK); + list_add_tail(&desc->node, &d40c->done); } -static void d40_log_lli_to_lcxa(struct d40_chan *chan, struct d40_desc *desc) +static int d40_desc_log_lli_to_lcxa(struct d40_chan *d40c, + struct d40_desc *d40d, + bool use_lcpa) { - struct d40_lcla_pool *pool = &chan->base->lcla_pool; - struct d40_log_lli_bidir *lli = &desc->lli_log; - int lli_current = desc->lli_current; - int lli_len = desc->lli_len; - bool cyclic = desc->cyclic; + struct d40_log_lli_bidir *lli = &d40d->lli_log; int curr_lcla = -EINVAL; int first_lcla = 0; - bool linkback; + bool use_esram_lcla = d40c->base->plat_data->use_esram_lcla; - /* - * We may have partially running cyclic transfers, in case we did't get - * enough LCLA entries. - */ - linkback = cyclic && lli_current == 0; + if ((d40d->lli_len - d40d->lli_current) > 1 || + d40d->cyclic || !use_lcpa) { - /* - * For linkback, we need one LCLA even with only one link, because we - * can't link back to the one in LCPA space - */ - if (linkback || (lli_len - lli_current > 1)) { - curr_lcla = d40_lcla_alloc_one(chan, desc); + curr_lcla = d40_lcla_alloc_one(d40c, d40d); first_lcla = curr_lcla; } - /* - * For linkback, we normally load the LCPA in the loop since we need to - * link it to the second LCLA and not the first. However, if we - * couldn't even get a first LCLA, then we have to run in LCPA and - * reload manually. - */ - if (!linkback || curr_lcla == -EINVAL) { - unsigned int flags = 0; - - if (curr_lcla == -EINVAL) - flags |= LLI_TERM_INT; - - d40_log_lli_lcpa_write(chan->lcpa, - &lli->dst[lli_current], - &lli->src[lli_current], + if (!d40d->cyclic && use_lcpa) { + d40_log_lli_lcpa_write(d40c->lcpa, + &lli->dst[d40d->lli_current], + &lli->src[d40d->lli_current], curr_lcla, - flags); - lli_current++; + curr_lcla == -EINVAL); + + d40d->lli_current++; } - if (curr_lcla < 0) - goto out; + /* + * Run only in LCPA space for non-cyclic. For cyclic, caller + * will handle the error. + */ + if (first_lcla < 0) + return first_lcla; - for (; lli_current < lli_len; lli_current++) { - unsigned int lcla_offset = chan->phy_chan->num * 1024 + + for (; d40d->lli_current < d40d->lli_len; d40d->lli_current++) { + unsigned int lcla_offset = d40c->phy_chan->num * 1024 + 8 * curr_lcla * 2; + struct d40_lcla_pool *pool = &d40c->base->lcla_pool; struct d40_log_lli *lcla = pool->base + lcla_offset; - unsigned int flags = 0; + int lli_current = d40d->lli_current; int next_lcla; + bool interrupt; - if (lli_current + 1 < lli_len) - next_lcla = d40_lcla_alloc_one(chan, desc); + if (d40d->lli_current + 1 < d40d->lli_len) + next_lcla = d40_lcla_alloc_one(d40c, d40d); else - next_lcla = linkback ? first_lcla : -EINVAL; + next_lcla = d40d->cyclic ? first_lcla : -EINVAL; - if (cyclic || next_lcla == -EINVAL) - flags |= LLI_TERM_INT; + interrupt = d40d->cyclic + ? d40d->txd.flags & DMA_PREP_INTERRUPT + : next_lcla == -EINVAL; - if (linkback && curr_lcla == first_lcla) { - /* First link goes in both LCPA and LCLA */ - d40_log_lli_lcpa_write(chan->lcpa, + if (d40d->cyclic && curr_lcla == first_lcla) { + /* + * For cyclic transactions, the first link is + * present in both LCPA and LCLA space because + * we can't link back to the one in LCPA space. + */ + d40_log_lli_lcpa_write(d40c->lcpa, &lli->dst[lli_current], &lli->src[lli_current], - next_lcla, flags); + next_lcla, + interrupt); } - /* - * One unused LCLA in the cyclic case if the very first - * next_lcla fails... - */ d40_log_lli_lcla_write(lcla, &lli->dst[lli_current], &lli->src[lli_current], - next_lcla, flags); - - dma_sync_single_range_for_device(chan->base->dev, - pool->dma_addr, lcla_offset, - 2 * sizeof(struct d40_log_lli), - DMA_TO_DEVICE); + next_lcla, + interrupt); + if (d40d->lli_current == d40d->lli_len - 1) + d40d->last_lcla = lcla; + /* + * Cache maintenance is not needed if lcla is + * mapped in esram + */ + if (!use_esram_lcla) { + dma_sync_single_range_for_device(d40c->base->dev, + pool->dma_addr, lcla_offset, + 2 * sizeof(struct d40_log_lli), + DMA_TO_DEVICE); + } curr_lcla = next_lcla; if (curr_lcla == -EINVAL || curr_lcla == first_lcla) { - lli_current++; + d40d->lli_current++; break; } + } -out: - desc->lli_current = lli_current; + return first_lcla; } static void d40_desc_load(struct d40_chan *d40c, struct d40_desc *d40d) { if (chan_is_physical(d40c)) { - d40_phy_lli_load(d40c, d40d); + d40_phy_lli_write(d40c->base->virtbase, + d40c->phy_chan->num, + d40d->lli_phy.dst, + d40d->lli_phy.src); d40d->lli_current = d40d->lli_len; } else - d40_log_lli_to_lcxa(d40c, d40d); + (void) d40_desc_log_lli_to_lcxa(d40c, d40d, true); } static struct d40_desc *d40_first_active_get(struct d40_chan *d40c) @@ -646,103 +722,198 @@ static struct d40_desc *d40_first_active_get(struct d40_chan *d40c) return d; } -/* remove desc from current queue and add it to the pending_queue */ static void d40_desc_queue(struct d40_chan *d40c, struct d40_desc *desc) { - d40_desc_remove(desc); - desc->is_in_client_list = false; - list_add_tail(&desc->node, &d40c->pending_queue); + list_add_tail(&desc->node, &d40c->queue); } -static struct d40_desc *d40_first_pending(struct d40_chan *d40c) +static struct d40_desc *d40_first_queued(struct d40_chan *d40c) { struct d40_desc *d; - if (list_empty(&d40c->pending_queue)) + if (list_empty(&d40c->queue)) return NULL; - d = list_first_entry(&d40c->pending_queue, + d = list_first_entry(&d40c->queue, struct d40_desc, node); return d; } -static struct d40_desc *d40_first_queued(struct d40_chan *d40c) +static struct d40_desc *d40_first_done(struct d40_chan *d40c) { - struct d40_desc *d; - - if (list_empty(&d40c->queue)) + if (list_empty(&d40c->done)) return NULL; - d = list_first_entry(&d40c->queue, - struct d40_desc, - node); - return d; + return list_first_entry(&d40c->done, struct d40_desc, node); } -static int d40_psize_2_burst_size(bool is_log, int psize) + +#ifdef CONFIG_PM +static void dma40_backup(void __iomem *baseaddr, u32 *backup, + u32 *regaddr, int num, bool save) { - if (is_log) { - if (psize == STEDMA40_PSIZE_LOG_1) - return 1; - } else { - if (psize == STEDMA40_PSIZE_PHY_1) - return 1; - } + int i; + + for (i = 0; i < num; i++) { + void __iomem *addr = baseaddr + regaddr[i]; - return 2 << psize; + if (save) + backup[i] = readl_relaxed(addr); + else + writel_relaxed(backup[i], addr); + } } -/* - * The dma only supports transmitting packages up to - * STEDMA40_MAX_SEG_SIZE << data_width. Calculate the total number of - * dma elements required to send the entire sg list - */ -static int d40_size_2_dmalen(int size, u32 data_width1, u32 data_width2) +static void d40_save_restore_registers(struct d40_base *base, bool save) { - int dmalen; - u32 max_w = max(data_width1, data_width2); - u32 min_w = min(data_width1, data_width2); - u32 seg_max = ALIGN(STEDMA40_MAX_SEG_SIZE << min_w, 1 << max_w); + int i; - if (seg_max > STEDMA40_MAX_SEG_SIZE) - seg_max -= (1 << max_w); + /* Enable all clocks -- revisit after HW bug is fixed */ + if (!save) + writel_relaxed(D40_DREG_GCC_ENABLE_ALL, + base->virtbase + D40_DREG_GCC); - if (!IS_ALIGNED(size, 1 << max_w)) - return -EINVAL; + /* Save/Restore channel specific registers */ + for (i = 0; i < base->num_phy_chans; i++) { + void __iomem *addr; + int idx; - if (size <= seg_max) - dmalen = 1; - else { - dmalen = size / seg_max; - if (dmalen * seg_max < size) - dmalen++; + if (base->phy_res[i].reserved) + continue; + + addr = base->virtbase + D40_DREG_PCBASE + i * D40_DREG_PCDELTA; + idx = i * ARRAY_SIZE(d40_backup_regs_chan); + + dma40_backup(addr, &base->reg_val_backup_chan[idx], + d40_backup_regs_chan, + ARRAY_SIZE(d40_backup_regs_chan), + save); } - return dmalen; + + /* Save/Restore global registers */ + dma40_backup(base->virtbase, base->reg_val_backup, + d40_backup_regs, ARRAY_SIZE(d40_backup_regs), + save); + + /* Save/Restore registers only existing on dma40 v3 and later */ + if (base->rev >= 3) + dma40_backup(base->virtbase, base->reg_val_backup_v3, + d40_backup_regs_v3, + ARRAY_SIZE(d40_backup_regs_v3), + save); +} +#else +static void d40_save_restore_registers(struct d40_base *base, bool save) +{ } +#endif -static int d40_sg_2_dmalen(struct scatterlist *sgl, int sg_len, - u32 data_width1, u32 data_width2) +static void d40_power_off(struct d40_base *base, int phy_num) { - struct scatterlist *sg; + u32 gcc; int i; - int len = 0; - int ret; + int j; + int p; - for_each_sg(sgl, sg, sg_len, i) { - ret = d40_size_2_dmalen(sg_dma_len(sg), - data_width1, data_width2); - if (ret < 0) - return ret; - len += ret; + /* + * Disable the rest of the code because of GCC register HW bugs on v1 + * which are not worth working around. Revisit later. + */ + return; + + /* + * Power off event group related to physical channel, if + * the other physical channels that belong to the same + * event group are not in use + */ + + for (j = 0; j < base->num_phy_chans; j += D40_GROUP_SIZE) { + + for (i = 0; i < 2; i++) { + p = (((phy_num & (base->num_phy_chans - 1)) + i) + & (D40_GROUP_SIZE - 1)) + j; + if (p == phy_num) + continue; + /* + * If another physical channel in the same group is + * allocated, just return. + */ + if (base->phy_res[p].allocated_dst == D40_ALLOC_PHY || + base->phy_res[p].allocated_src == D40_ALLOC_PHY) { + return; + } + } } - return len; + + /* The GCC register is protected via the usage_lock */ + gcc = readl(base->virtbase + D40_DREG_GCC); + + gcc &= ~D40_DREG_GCC_EVTGRP_ENA(D40_PHYS_TO_GROUP(phy_num), + D40_DREG_GCC_SRC); + gcc &= ~D40_DREG_GCC_EVTGRP_ENA(D40_PHYS_TO_GROUP(phy_num), + D40_DREG_GCC_DST); + + writel(gcc, base->virtbase + D40_DREG_GCC); } -/* Support functions for logical channels */ +static void d40_power_on(struct d40_base *base, int phy_num) +{ + u32 gcc; + + /* + * Disable the rest of the code because of GCC register HW bugs on v1 + * which are not worth working around. Revisit later. + */ + return; -static int d40_channel_execute_command(struct d40_chan *d40c, - enum d40_command command) + /* The GCC register is protected via the usage_lock */ + gcc = readl(base->virtbase + D40_DREG_GCC); + + gcc |= D40_DREG_GCC_EVTGRP_ENA(D40_PHYS_TO_GROUP(phy_num), + D40_DREG_GCC_SRC); + gcc |= D40_DREG_GCC_EVTGRP_ENA(D40_PHYS_TO_GROUP(phy_num), + D40_DREG_GCC_DST); + + writel(gcc, base->virtbase + D40_DREG_GCC); +} + +static void d40_usage_inc(struct d40_chan *d40c) +{ + unsigned long flags; + + spin_lock_irqsave(&d40c->base->usage_lock, flags); + + d40c->base->usage++; + + if (d40c->base->usage == 1) + pm_runtime_get_sync(d40c->base->dev); + + d40_power_on(d40c->base, d40c->phy_chan->num); + + spin_unlock_irqrestore(&d40c->base->usage_lock, flags); +} + +static void d40_usage_dec(struct d40_chan *d40c) +{ + unsigned long flags; + + spin_lock_irqsave(&d40c->base->usage_lock, flags); + + d40_power_off(d40c->base, d40c->phy_chan->num); + + d40c->base->usage--; + + if (d40c->base->usage == 0) { + pm_runtime_mark_last_busy(d40c->base->dev); + pm_runtime_put_autosuspend(d40c->base->dev); + } + + spin_unlock_irqrestore(&d40c->base->usage_lock, flags); +} + +static int __d40_execute_command_phy(struct d40_chan *d40c, + enum d40_command command) { u32 status; int i; @@ -751,6 +922,12 @@ static int d40_channel_execute_command(struct d40_chan *d40c, unsigned long flags; u32 wmask; + if (command == D40_DMA_STOP) { + ret = __d40_execute_command_phy(d40c, D40_DMA_SUSPEND_REQ); + if (ret) + return ret; + } + spin_lock_irqsave(&d40c->base->execmd_lock, flags); if (d40c->phy_chan->num % 2 == 0) @@ -767,6 +944,11 @@ static int d40_channel_execute_command(struct d40_chan *d40c, goto done; } +#ifdef CONFIG_STE_DMA40_DEBUG + if (command == D40_DMA_RUN) + sted40_history_snapshot(); +#endif + wmask = 0xffffffff & ~(D40_CHAN_POS_MASK(d40c->phy_chan->num)); writel(wmask | (command << D40_CHAN_POS(d40c->phy_chan->num)), active_reg); @@ -795,6 +977,9 @@ static int d40_channel_execute_command(struct d40_chan *d40c, "unable to suspend the chl %d (log: %d) status %x\n", d40c->phy_chan->num, d40c->log_num, status); +#ifdef CONFIG_STE_DMA40_DEBUG + sted40_history_dump(); +#endif dump_stack(); ret = -EBUSY; } @@ -808,43 +993,26 @@ done: static void d40_term_all(struct d40_chan *d40c) { struct d40_desc *d40d; - struct d40_desc *_d; - /* Release active descriptors */ - while ((d40d = d40_first_active_get(d40c))) { + /* Release completed descriptors */ + while ((d40d = d40_first_done(d40c))) { d40_desc_remove(d40d); d40_desc_free(d40c, d40d); } - /* Release queued descriptors waiting for transfer */ - while ((d40d = d40_first_queued(d40c))) { + /* Release active descriptors */ + while ((d40d = d40_first_active_get(d40c))) { d40_desc_remove(d40d); d40_desc_free(d40c, d40d); } - /* Release pending descriptors */ - while ((d40d = d40_first_pending(d40c))) { + /* Release queued descriptors waiting for transfer */ + while ((d40d = d40_first_queued(d40c))) { d40_desc_remove(d40d); d40_desc_free(d40c, d40d); } - /* Release client owned descriptors */ - if (!list_empty(&d40c->client)) - list_for_each_entry_safe(d40d, _d, &d40c->client, node) { - d40_desc_remove(d40d); - d40_desc_free(d40c, d40d); - } - - /* Release descriptors in prepare queue */ - if (!list_empty(&d40c->prepare_queue)) - list_for_each_entry_safe(d40d, _d, - &d40c->prepare_queue, node) { - d40_desc_remove(d40d); - d40_desc_free(d40c, d40d); - } - d40c->pending_tx = 0; - d40c->busy = false; } static void __d40_config_set_event(struct d40_chan *d40c, bool enable, @@ -884,10 +1052,6 @@ static void __d40_config_set_event(struct d40_chan *d40c, bool enable, static void d40_config_set_event(struct d40_chan *d40c, bool do_enable) { - unsigned long flags; - - spin_lock_irqsave(&d40c->phy_chan->lock, flags); - /* Enable event line connected to device (or memcpy) */ if ((d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_MEM) || (d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_PERIPH)) { @@ -903,8 +1067,6 @@ static void d40_config_set_event(struct d40_chan *d40c, bool do_enable) __d40_config_set_event(d40c, do_enable, event, D40_CHAN_REG_SDLNK); } - - spin_unlock_irqrestore(&d40c->phy_chan->lock, flags); } static u32 d40_chan_has_events(struct d40_chan *d40c) @@ -918,6 +1080,61 @@ static u32 d40_chan_has_events(struct d40_chan *d40c) return val; } +static int +__d40_execute_command_log(struct d40_chan *d40c, enum d40_command command) +{ + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&d40c->phy_chan->lock, flags); + + switch (command) { + case D40_DMA_STOP: + case D40_DMA_SUSPEND_REQ: + ret = __d40_execute_command_phy(d40c, D40_DMA_SUSPEND_REQ); + if (ret) + goto out; + + d40_config_set_event(d40c, false); + + if (d40_chan_has_events(d40c)) + ret = __d40_execute_command_phy(d40c, D40_DMA_RUN); + else if (command == D40_DMA_STOP) + ret = __d40_execute_command_phy(d40c, command); + break; + + case D40_DMA_RUN: + if (d40c->base->rev == 0) { + ret = __d40_execute_command_phy(d40c, + D40_DMA_SUSPEND_REQ); + if (ret) + goto out; + } + + d40_config_set_event(d40c, true); + + ret = __d40_execute_command_phy(d40c, command); + break; + + case D40_DMA_SUSPENDED: + BUG(); + break; + } + +out: + spin_unlock_irqrestore(&d40c->phy_chan->lock, flags); + return ret; +} + +static int d40_channel_execute_command(struct d40_chan *d40c, + enum d40_command command) +{ + if (chan_is_logical(d40c)) + return __d40_execute_command_log(d40c, command); + else + return __d40_execute_command_phy(d40c, command); +} + static u32 d40_get_prmo(struct d40_chan *d40c) { static const unsigned int phy_map[] = { @@ -972,6 +1189,10 @@ static void d40_config_write(struct d40_chan *d40c) /* Set LIDX for lcla */ writel(lidx, chanbase + D40_CHAN_REG_SSELT); writel(lidx, chanbase + D40_CHAN_REG_SDELT); + + /* Clear LNK which will be used by d40_chan_has_events() */ + writel(0, chanbase + D40_CHAN_REG_SSLNK); + writel(0, chanbase + D40_CHAN_REG_SDLNK); } } @@ -980,13 +1201,12 @@ static u32 d40_residue(struct d40_chan *d40c) u32 num_elt; if (chan_is_logical(d40c)) - num_elt = (readl(&d40c->lcpa->lcsp2) & D40_MEM_LCSP2_ECNT_MASK) - >> D40_MEM_LCSP2_ECNT_POS; - else { - u32 val = readl(chan_base(d40c) + D40_CHAN_REG_SDELT); - num_elt = (val & D40_SREG_ELEM_PHY_ECNT_MASK) - >> D40_SREG_ELEM_PHY_ECNT_POS; - } + num_elt = (readl(&d40c->lcpa->lcsp2) & + D40_MEM_LCSP2_ECNT_MASK) >> D40_MEM_LCSP2_ECNT_POS; + else + num_elt = (readl(chan_base(d40c) + D40_CHAN_REG_SDELT) & + D40_SREG_ELEM_PHY_ECNT_MASK) >> + D40_SREG_ELEM_PHY_ECNT_POS; return num_elt * (1 << d40c->dma_cfg.dst_info.data_width); } @@ -1004,33 +1224,31 @@ static bool d40_tx_is_linked(struct d40_chan *d40c) return is_link; } -static int d40_pause(struct d40_chan *d40c) +static int d40_pause(struct dma_chan *chan) { + struct d40_chan *d40c = + container_of(chan, struct d40_chan, chan); int res = 0; unsigned long flags; if (!d40c->busy) return 0; + d40_usage_inc(d40c); spin_lock_irqsave(&d40c->lock, flags); res = d40_channel_execute_command(d40c, D40_DMA_SUSPEND_REQ); - if (res == 0) { - if (chan_is_logical(d40c)) { - d40_config_set_event(d40c, false); - /* Resume the other logical channels if any */ - if (d40_chan_has_events(d40c)) - res = d40_channel_execute_command(d40c, - D40_DMA_RUN); - } - } + + d40_usage_dec(d40c); spin_unlock_irqrestore(&d40c->lock, flags); return res; } -static int d40_resume(struct d40_chan *d40c) +static int d40_resume(struct dma_chan *chan) { + struct d40_chan *d40c = + container_of(chan, struct d40_chan, chan); int res = 0; unsigned long flags; @@ -1039,43 +1257,17 @@ static int d40_resume(struct d40_chan *d40c) spin_lock_irqsave(&d40c->lock, flags); - if (d40c->base->rev == 0) - if (chan_is_logical(d40c)) { - res = d40_channel_execute_command(d40c, - D40_DMA_SUSPEND_REQ); - goto no_suspend; - } + d40_usage_inc(d40c); /* If bytes left to transfer or linked tx resume job */ - if (d40_residue(d40c) || d40_tx_is_linked(d40c)) { - - if (chan_is_logical(d40c)) - d40_config_set_event(d40c, true); - + if (d40_residue(d40c) || d40_tx_is_linked(d40c)) res = d40_channel_execute_command(d40c, D40_DMA_RUN); - } -no_suspend: + d40_usage_dec(d40c); spin_unlock_irqrestore(&d40c->lock, flags); return res; } -static int d40_terminate_all(struct d40_chan *chan) -{ - unsigned long flags; - int ret = 0; - - ret = d40_pause(chan); - if (!ret && chan_is_physical(chan)) - ret = d40_channel_execute_command(chan, D40_DMA_STOP); - - spin_lock_irqsave(&chan->lock, flags); - d40_term_all(chan); - spin_unlock_irqrestore(&chan->lock, flags); - - return ret; -} - static dma_cookie_t d40_tx_submit(struct dma_async_tx_descriptor *tx) { struct d40_chan *d40c = container_of(tx->chan, @@ -1093,7 +1285,8 @@ static dma_cookie_t d40_tx_submit(struct dma_async_tx_descriptor *tx) d40d->txd.cookie = d40c->chan.cookie; - d40_desc_queue(d40c, d40d); + if (!d40c->cdesc) + d40_desc_queue(d40c, d40d); spin_unlock_irqrestore(&d40c->lock, flags); @@ -1102,20 +1295,6 @@ static dma_cookie_t d40_tx_submit(struct dma_async_tx_descriptor *tx) static int d40_start(struct d40_chan *d40c) { - if (d40c->base->rev == 0) { - int err; - - if (chan_is_logical(d40c)) { - err = d40_channel_execute_command(d40c, - D40_DMA_SUSPEND_REQ); - if (err) - return err; - } - } - - if (chan_is_logical(d40c)) - d40_config_set_event(d40c, true); - return d40_channel_execute_command(d40c, D40_DMA_RUN); } @@ -1128,7 +1307,10 @@ static struct d40_desc *d40_queue_start(struct d40_chan *d40c) d40d = d40_first_queued(d40c); if (d40d != NULL) { - d40c->busy = true; + if (!d40c->busy) { + d40_usage_inc(d40c); + d40c->busy = true; + } /* Remove from queue */ d40_desc_remove(d40d); @@ -1153,47 +1335,55 @@ static struct d40_desc *d40_queue_start(struct d40_chan *d40c) static void dma_tc_handle(struct d40_chan *d40c) { struct d40_desc *d40d; + bool islastactive; + + if (d40c->cdesc) { + d40c->pending_tx++; + tasklet_schedule(&d40c->tasklet); + return; + } /* Get first active entry from list */ +redo: d40d = d40_first_active_get(d40c); if (d40d == NULL) return; - if (d40d->cyclic) { - /* - * If this was a paritially loaded list, we need to reloaded - * it, and only when the list is completed. We need to check - * for done because the interrupt will hit for every link, and - * not just the last one. - */ - if (d40d->lli_current < d40d->lli_len - && !d40_tx_is_linked(d40c) - && !d40_residue(d40c)) { - d40_lcla_free_all(d40c, d40d); - d40_desc_load(d40c, d40d); - (void) d40_start(d40c); - - if (d40d->lli_current == d40d->lli_len) - d40d->lli_current = 0; - } - } else { - d40_lcla_free_all(d40c, d40d); + d40_lcla_free_all(d40c, d40d); - if (d40d->lli_current < d40d->lli_len) { - d40_desc_load(d40c, d40d); - /* Start dma job */ - (void) d40_start(d40c); - return; - } + if (d40d->lli_current < d40d->lli_len) { + d40_desc_load(d40c, d40d); + /* Start dma job */ + (void) d40_start(d40c); + return; + } - if (d40_queue_start(d40c) == NULL) - d40c->busy = false; + /* + * More than one active happens when we have + * hw linked transfers. + */ + islastactive = list_is_last(&d40d->node, &d40c->active); + if (islastactive && d40_queue_start(d40c) == NULL) { + d40c->busy = false; + d40_usage_dec(d40c); } + d40_desc_remove(d40d); + d40_desc_done(d40c, d40d); + d40c->pending_tx++; tasklet_schedule(&d40c->tasklet); + /* + * When we have multiple active transfers, there is a chance that we + * might miss some link interrupts if the time to perform each link is + * very small (mostly with mem-to-mem transfers). So, if the hardware + * is not transmitting any more links, assume that all the active + * transfers are complete. + */ + if (!islastactive && !d40_tx_is_linked(d40c)) + goto redo; } static void dma_tasklet(unsigned long data) @@ -1206,13 +1396,16 @@ static void dma_tasklet(unsigned long data) spin_lock_irqsave(&d40c->lock, flags); - /* Get first active entry from list */ - d40d = d40_first_active_get(d40c); - if (d40d == NULL) - goto err; + if (d40c->cdesc) + d40d = d40c->cdesc->d40d; + else { + /* Get first active entry from list */ + d40d = d40_first_done(d40c); + if (d40d == NULL) + goto err; - if (!d40d->cyclic) d40c->completed = d40d->txd.cookie; + } /* * If terminating a channel pending_tx is set to zero. @@ -1224,20 +1417,28 @@ static void dma_tasklet(unsigned long data) } /* Callback to client */ - callback = d40d->txd.callback; - callback_param = d40d->txd.callback_param; - if (!d40d->cyclic) { + if (d40c->cdesc) { + callback = d40c->cdesc->period_callback; + callback_param = d40c->cdesc->period_callback_param; + + if (!callback) { + callback = d40d->txd.callback; + callback_param = d40d->txd.callback_param; + } + } else { + callback = d40d->txd.callback; + callback_param = d40d->txd.callback_param; + if (async_tx_test_ack(&d40d->txd)) { + d40_pool_lli_free(d40c, d40d); d40_desc_remove(d40d); d40_desc_free(d40c, d40d); - } else { - if (!d40d->is_in_client_list) { - d40_desc_remove(d40d); - d40_lcla_free_all(d40c, d40d); - list_add_tail(&d40d->node, &d40c->client); - d40d->is_in_client_list = true; - } + } else if (!d40d->is_in_client_list) { + d40_desc_remove(d40d); + d40_lcla_free_all(d40c, d40d); + list_add_tail(&d40d->node, &d40c->client); + d40d->is_in_client_list = true; } } @@ -1253,8 +1454,8 @@ static void dma_tasklet(unsigned long data) return; - err: - /* Rescue manoeuvre if receiving double interrupts */ +err: + /* Rescue manouver if receiving double interrupts */ if (d40c->pending_tx > 0) d40c->pending_tx--; spin_unlock_irqrestore(&d40c->lock, flags); @@ -1262,7 +1463,7 @@ static void dma_tasklet(unsigned long data) static irqreturn_t d40_handle_interrupt(int irq, void *data) { - static const struct d40_interrupt_lookup il[] = { + static struct d40_interrupt_lookup il[] = { {D40_DREG_LCTIS0, D40_DREG_LCICR0, false, 0}, {D40_DREG_LCTIS1, D40_DREG_LCICR1, false, 32}, {D40_DREG_LCTIS2, D40_DREG_LCICR2, false, 64}, @@ -1285,7 +1486,9 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data) struct d40_base *base = data; spin_lock_irqsave(&base->interrupt_lock, flags); - +#ifdef CONFIG_STE_DMA40_DEBUG + sted40_history_text("IRQ enter"); +#endif /* Read interrupt status of both logical and physical channels */ for (i = 0; i < ARRAY_SIZE(il); i++) regs[i] = readl(base->virtbase + il[i].src); @@ -1302,24 +1505,40 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data) row = chan / BITS_PER_LONG; idx = chan & (BITS_PER_LONG - 1); - /* ACK interrupt */ - writel(1 << idx, base->virtbase + il[row].clr); - if (il[row].offset == D40_PHY_CHAN) d40c = base->lookup_phy_chans[idx]; else d40c = base->lookup_log_chans[il[row].offset + idx]; + + if (!d40c) { + /* + * No error because this can happen if something else + * in the system is using the channel. + */ + continue; + } + + /* ACK interrupt */ + writel(1 << idx, base->virtbase + il[row].clr); + spin_lock(&d40c->lock); - if (!il[row].is_error) + if (!il[row].is_error) { + dma_tc_handle(d40c); - else + } else { d40_err(base->dev, "IRQ chan: %ld offset %d idx %d\n", chan, il[row].offset, idx); +#ifdef CONFIG_STE_DMA40_DEBUG + sted40_history_dump(); +#endif + } spin_unlock(&d40c->lock); } - +#ifdef CONFIG_STE_DMA40_DEBUG + sted40_history_text("IRQ leave"); +#endif spin_unlock_irqrestore(&base->interrupt_lock, flags); return IRQ_HANDLED; @@ -1339,17 +1558,14 @@ static int d40_validate_conf(struct d40_chan *d40c, } if (conf->dst_dev_type != STEDMA40_DEV_DST_MEMORY && - d40c->base->plat_data->dev_tx[conf->dst_dev_type] == 0 && - d40c->runtime_addr == 0) { - + d40c->base->plat_data->dev_tx[conf->dst_dev_type] == 0) { chan_err(d40c, "Invalid TX channel address (%d)\n", conf->dst_dev_type); res = -EINVAL; } if (conf->src_dev_type != STEDMA40_DEV_SRC_MEMORY && - d40c->base->plat_data->dev_rx[conf->src_dev_type] == 0 && - d40c->runtime_addr == 0) { + d40c->base->plat_data->dev_rx[conf->src_dev_type] == 0) { chan_err(d40c, "Invalid RX channel address (%d)\n", conf->src_dev_type); res = -EINVAL; @@ -1388,27 +1604,19 @@ static int d40_validate_conf(struct d40_chan *d40c, res = -EINVAL; } - if (d40_psize_2_burst_size(is_log, conf->src_info.psize) * - (1 << conf->src_info.data_width) != - d40_psize_2_burst_size(is_log, conf->dst_info.psize) * - (1 << conf->dst_info.data_width)) { - /* - * The DMAC hardware only supports - * src (burst x width) == dst (burst x width) - */ - - chan_err(d40c, "src (burst x width) != dst (burst x width)\n"); - res = -EINVAL; - } - return res; } -static bool d40_alloc_mask_set(struct d40_phy_res *phy, bool is_src, - int log_event_line, bool is_log) +static bool d40_alloc_mask_set(struct d40_phy_res *phy, + bool is_src, int log_event_line, bool is_log, + bool *first_user) { unsigned long flags; spin_lock_irqsave(&phy->lock, flags); + + *first_user = ((phy->allocated_src | phy->allocated_dst) + == D40_ALLOC_FREE); + if (!is_log) { /* Physical interrupts are masked per physical full channel */ if (phy->allocated_src == D40_ALLOC_FREE && @@ -1489,7 +1697,7 @@ out: return is_free; } -static int d40_allocate_channel(struct d40_chan *d40c) +static int d40_allocate_channel(struct d40_chan *d40c, bool *first_phy_user) { int dev_type; int event_group; @@ -1525,7 +1733,8 @@ static int d40_allocate_channel(struct d40_chan *d40c) for (i = 0; i < d40c->base->num_phy_chans; i++) { if (d40_alloc_mask_set(&phys[i], is_src, - 0, is_log)) + 0, is_log, + first_phy_user)) goto found_phy; } } else @@ -1533,9 +1742,9 @@ static int d40_allocate_channel(struct d40_chan *d40c) int phy_num = j + event_group * 2; for (i = phy_num; i < phy_num + 2; i++) { if (d40_alloc_mask_set(&phys[i], - is_src, - 0, - is_log)) + is_src, 0, + is_log, + first_phy_user)) goto found_phy; } } @@ -1551,6 +1760,25 @@ found_phy: /* Find logical channel */ for (j = 0; j < d40c->base->num_phy_chans; j += 8) { int phy_num = j + event_group * 2; + + if (d40c->dma_cfg.use_fixed_channel) { + i = d40c->dma_cfg.phy_channel; + + if ((i != phy_num) && (i != phy_num + 1)) { + dev_err(chan2dev(d40c), + "invalid fixed phy channel %d\n", i); + return -EINVAL; + } + + if (d40_alloc_mask_set(&phys[i], is_src, event_line, is_log, + first_phy_user)) + goto found_log; + + dev_err(chan2dev(d40c), + "could not allocated fixed phy channel %d\n", i); + return -EINVAL; + } + /* * Spread logical channels across all available physical rather * than pack every logical channel at the first available phy @@ -1559,13 +1787,15 @@ found_phy: if (is_src) { for (i = phy_num; i < phy_num + 2; i++) { if (d40_alloc_mask_set(&phys[i], is_src, - event_line, is_log)) + event_line, is_log, + first_phy_user)) goto found_log; } } else { for (i = phy_num + 1; i >= phy_num; i--) { if (d40_alloc_mask_set(&phys[i], is_src, - event_line, is_log)) + event_line, is_log, + first_phy_user)) goto found_log; } } @@ -1607,7 +1837,6 @@ static int d40_config_memcpy(struct d40_chan *d40c) return 0; } - static int d40_free_dma(struct d40_chan *d40c) { @@ -1615,10 +1844,20 @@ static int d40_free_dma(struct d40_chan *d40c) u32 event; struct d40_phy_res *phy = d40c->phy_chan; bool is_src; + struct d40_desc *d; + struct d40_desc *_d; /* Terminate all queued and active transfers */ d40_term_all(d40c); + /* Release client owned descriptors */ + if (!list_empty(&d40c->client)) + list_for_each_entry_safe(d, _d, &d40c->client, node) { + d40_pool_lli_free(d40c, d); + d40_desc_remove(d); + d40_desc_free(d40c, d); + } + if (phy == NULL) { chan_err(d40c, "phy == null\n"); return -EINVAL; @@ -1642,48 +1881,29 @@ static int d40_free_dma(struct d40_chan *d40c) return -EINVAL; } - res = d40_channel_execute_command(d40c, D40_DMA_SUSPEND_REQ); + d40_usage_inc(d40c); + + res = d40_channel_execute_command(d40c, D40_DMA_STOP); if (res) { - chan_err(d40c, "suspend failed\n"); + chan_err(d40c, "stop failed\n"); + d40_usage_dec(d40c); return res; } - if (chan_is_logical(d40c)) { - /* Release logical channel, deactivate the event line */ + d40_alloc_mask_free(phy, is_src, chan_is_logical(d40c) ? event : 0); - d40_config_set_event(d40c, false); + if (chan_is_logical(d40c)) d40c->base->lookup_log_chans[d40c->log_num] = NULL; + else + d40c->base->lookup_phy_chans[phy->num] = NULL; - /* - * Check if there are more logical allocation - * on this phy channel. - */ - if (!d40_alloc_mask_free(phy, is_src, event)) { - /* Resume the other logical channels if any */ - if (d40_chan_has_events(d40c)) { - res = d40_channel_execute_command(d40c, - D40_DMA_RUN); - if (res) { - chan_err(d40c, - "Executing RUN command\n"); - return res; - } - } - return 0; - } - } else { - (void) d40_alloc_mask_free(phy, is_src, 0); - } + d40_usage_dec(d40c); + if (d40c->busy) + d40_usage_dec(d40c); + d40c->busy = false; - /* Release physical channel */ - res = d40_channel_execute_command(d40c, D40_DMA_STOP); - if (res) { - chan_err(d40c, "Failed to stop channel\n"); - return res; - } d40c->phy_chan = NULL; d40c->configured = false; - d40c->base->lookup_phy_chans[phy->num] = NULL; return 0; } @@ -1737,109 +1957,17 @@ _exit: } - -static u32 stedma40_residue(struct dma_chan *chan) -{ - struct d40_chan *d40c = - container_of(chan, struct d40_chan, chan); - u32 bytes_left; - unsigned long flags; - - spin_lock_irqsave(&d40c->lock, flags); - bytes_left = d40_residue(d40c); - spin_unlock_irqrestore(&d40c->lock, flags); - - return bytes_left; -} - -static int -d40_prep_sg_log(struct d40_chan *chan, struct d40_desc *desc, - struct scatterlist *sg_src, struct scatterlist *sg_dst, - unsigned int sg_len, dma_addr_t src_dev_addr, - dma_addr_t dst_dev_addr) -{ - struct stedma40_chan_cfg *cfg = &chan->dma_cfg; - struct stedma40_half_channel_info *src_info = &cfg->src_info; - struct stedma40_half_channel_info *dst_info = &cfg->dst_info; - int ret; - - ret = d40_log_sg_to_lli(sg_src, sg_len, - src_dev_addr, - desc->lli_log.src, - chan->log_def.lcsp1, - src_info->data_width, - dst_info->data_width); - - ret = d40_log_sg_to_lli(sg_dst, sg_len, - dst_dev_addr, - desc->lli_log.dst, - chan->log_def.lcsp3, - dst_info->data_width, - src_info->data_width); - - return ret < 0 ? ret : 0; -} - -static int -d40_prep_sg_phy(struct d40_chan *chan, struct d40_desc *desc, - struct scatterlist *sg_src, struct scatterlist *sg_dst, - unsigned int sg_len, dma_addr_t src_dev_addr, - dma_addr_t dst_dev_addr) -{ - struct stedma40_chan_cfg *cfg = &chan->dma_cfg; - struct stedma40_half_channel_info *src_info = &cfg->src_info; - struct stedma40_half_channel_info *dst_info = &cfg->dst_info; - unsigned long flags = 0; - int ret; - - if (desc->cyclic) - flags |= LLI_CYCLIC | LLI_TERM_INT; - - ret = d40_phy_sg_to_lli(sg_src, sg_len, src_dev_addr, - desc->lli_phy.src, - virt_to_phys(desc->lli_phy.src), - chan->src_def_cfg, - src_info, dst_info, flags); - - ret = d40_phy_sg_to_lli(sg_dst, sg_len, dst_dev_addr, - desc->lli_phy.dst, - virt_to_phys(desc->lli_phy.dst), - chan->dst_def_cfg, - dst_info, src_info, flags); - - dma_sync_single_for_device(chan->base->dev, desc->lli_pool.dma_addr, - desc->lli_pool.size, DMA_TO_DEVICE); - - return ret < 0 ? ret : 0; -} - - static struct d40_desc * d40_prep_desc(struct d40_chan *chan, struct scatterlist *sg, unsigned int sg_len, unsigned long dma_flags) { - struct stedma40_chan_cfg *cfg = &chan->dma_cfg; struct d40_desc *desc; - int ret; desc = d40_desc_get(chan); if (!desc) return NULL; - desc->lli_len = d40_sg_2_dmalen(sg, sg_len, cfg->src_info.data_width, - cfg->dst_info.data_width); - if (desc->lli_len < 0) { - chan_err(chan, "Unaligned size\n"); - goto err; - } - - ret = d40_pool_lli_alloc(chan, desc, desc->lli_len); - if (ret < 0) { - chan_err(chan, "Could not allocate lli\n"); - goto err; - } - - + desc->lli_len = sg_len; desc->lli_current = 0; desc->txd.flags = dma_flags; desc->txd.tx_submit = d40_tx_submit; @@ -1847,95 +1975,99 @@ d40_prep_desc(struct d40_chan *chan, struct scatterlist *sg, dma_async_tx_descriptor_init(&desc->txd, &chan->chan); return desc; - -err: - d40_desc_free(chan, desc); - return NULL; -} - -static dma_addr_t -d40_get_dev_addr(struct d40_chan *chan, enum dma_data_direction direction) -{ - struct stedma40_platform_data *plat = chan->base->plat_data; - struct stedma40_chan_cfg *cfg = &chan->dma_cfg; - dma_addr_t addr = 0; - - if (chan->runtime_addr) - return chan->runtime_addr; - - if (direction == DMA_FROM_DEVICE) - addr = plat->dev_rx[cfg->src_dev_type]; - else if (direction == DMA_TO_DEVICE) - addr = plat->dev_tx[cfg->dst_dev_type]; - - return addr; } -static struct dma_async_tx_descriptor * -d40_prep_sg(struct dma_chan *dchan, struct scatterlist *sg_src, - struct scatterlist *sg_dst, unsigned int sg_len, - enum dma_data_direction direction, unsigned long dma_flags) +struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan, + struct scatterlist *sgl_dst, + struct scatterlist *sgl_src, + unsigned int sgl_len, + unsigned long dma_flags) { - struct d40_chan *chan = container_of(dchan, struct d40_chan, chan); - dma_addr_t src_dev_addr = 0; - dma_addr_t dst_dev_addr = 0; - struct d40_desc *desc; + int res; + struct d40_desc *d40d; + struct d40_chan *d40c = container_of(chan, struct d40_chan, + chan); unsigned long flags; - int ret; - if (!chan->phy_chan) { - chan_err(chan, "Cannot prepare unallocated channel\n"); - return NULL; + if (d40c->phy_chan == NULL) { + chan_err(d40c, "Unallocated channel.\n"); + return ERR_PTR(-EINVAL); } + spin_lock_irqsave(&d40c->lock, flags); - spin_lock_irqsave(&chan->lock, flags); + d40d = d40_prep_desc(d40c, sgl_dst, sgl_len, dma_flags); - desc = d40_prep_desc(chan, sg_src, sg_len, dma_flags); - if (desc == NULL) + if (!d40d) goto err; - if (sg_next(&sg_src[sg_len - 1]) == sg_src) - desc->cyclic = true; - - if (direction != DMA_NONE) { - dma_addr_t dev_addr = d40_get_dev_addr(chan, direction); + if (chan_is_logical(d40c)) { - if (direction == DMA_FROM_DEVICE) - src_dev_addr = dev_addr; - else if (direction == DMA_TO_DEVICE) - dst_dev_addr = dev_addr; - } + if (d40_pool_lli_alloc(d40c, d40d, sgl_len, true) < 0) { + chan_err(d40c, "Out of memory\n"); + goto err; + } - if (chan_is_logical(chan)) - ret = d40_prep_sg_log(chan, desc, sg_src, sg_dst, - sg_len, src_dev_addr, dst_dev_addr); - else - ret = d40_prep_sg_phy(chan, desc, sg_src, sg_dst, - sg_len, src_dev_addr, dst_dev_addr); + (void) d40_log_sg_to_lli(sgl_src, + sgl_len, + d40d->lli_log.src, + d40c->log_def.lcsp1, + d40c->dma_cfg.src_info.data_width); + + (void) d40_log_sg_to_lli(sgl_dst, + sgl_len, + d40d->lli_log.dst, + d40c->log_def.lcsp3, + d40c->dma_cfg.dst_info.data_width); + } else { + if (d40_pool_lli_alloc(d40c, d40d, sgl_len, false) < 0) { + chan_err(d40c, "Out of memory\n"); + goto err; + } - if (ret) { - chan_err(chan, "Failed to prepare %s sg job: %d\n", - chan_is_logical(chan) ? "log" : "phy", ret); - goto err; + res = d40_phy_sg_to_lli(sgl_src, + sgl_len, + 0, + d40d->lli_phy.src, + virt_to_phys(d40d->lli_phy.src), + d40c->src_def_cfg, + d40c->dma_cfg.src_info.data_width, + d40c->dma_cfg.src_info.psize, + false, + false); + + if (res < 0) + goto err; + + res = d40_phy_sg_to_lli(sgl_dst, + sgl_len, + 0, + d40d->lli_phy.dst, + virt_to_phys(d40d->lli_phy.dst), + d40c->dst_def_cfg, + d40c->dma_cfg.dst_info.data_width, + d40c->dma_cfg.dst_info.psize, + false, + false); + + if (res < 0) + goto err; + + dma_sync_single_for_device(d40c->base->dev, + d40d->lli_pool.dma_addr, + d40d->lli_pool.size, DMA_TO_DEVICE); } - /* - * add descriptor to the prepare queue in order to be able - * to free them later in terminate_all - */ - list_add_tail(&desc->node, &chan->prepare_queue); - - spin_unlock_irqrestore(&chan->lock, flags); - - return &desc->txd; + spin_unlock_irqrestore(&d40c->lock, flags); + return &d40d->txd; err: - if (desc) - d40_desc_free(chan, desc); - spin_unlock_irqrestore(&chan->lock, flags); + if (d40d) + d40_desc_free(d40c, d40d); + spin_unlock_irqrestore(&d40c->lock, flags); return NULL; } +EXPORT_SYMBOL(stedma40_memcpy_sg); bool stedma40_filter(struct dma_chan *chan, void *data) { @@ -1998,11 +2130,13 @@ static int d40_alloc_chan_resources(struct dma_chan *chan) struct d40_chan *d40c = container_of(chan, struct d40_chan, chan); bool is_free_phy; + + d40_usage_inc(d40c); + spin_lock_irqsave(&d40c->lock, flags); d40c->completed = chan->cookie = 1; - /* If no dma configuration is set use default configuration (memcpy) */ if (!d40c->configured) { err = d40_config_memcpy(d40c); if (err) { @@ -2010,11 +2144,11 @@ static int d40_alloc_chan_resources(struct dma_chan *chan) goto fail; } } - is_free_phy = (d40c->phy_chan == NULL); - err = d40_allocate_channel(d40c); + err = d40_allocate_channel(d40c, &is_free_phy); if (err) { chan_err(d40c, "Failed to allocate channel\n"); + d40c->configured = false; goto fail; } @@ -2030,21 +2164,30 @@ static int d40_alloc_chan_resources(struct dma_chan *chan) if (d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_MEM) d40c->lcpa = d40c->base->lcpa_base + - d40c->dma_cfg.src_dev_type * D40_LCPA_CHAN_SIZE; + d40c->dma_cfg.src_dev_type * + D40_LCPA_CHAN_SIZE; else d40c->lcpa = d40c->base->lcpa_base + - d40c->dma_cfg.dst_dev_type * - D40_LCPA_CHAN_SIZE + D40_LCPA_CHAN_DST_DELTA; + d40c->dma_cfg.dst_dev_type * + D40_LCPA_CHAN_SIZE + D40_LCPA_CHAN_DST_DELTA; } + dev_info(chan2dev(d40c), "allocated %s channel (phy %d%s)\n", + chan_is_logical(d40c) ? "logical" : "physical", + d40c->phy_chan->num, + d40c->dma_cfg.use_fixed_channel ? ", fixed" : ""); + + /* * Only write channel configuration to the DMA if the physical * resource is free. In case of multiple logical channels * on the same physical resource, only the first write is necessary. */ + if (is_free_phy) d40_config_write(d40c); fail: + d40_usage_dec(d40c); spin_unlock_irqrestore(&d40c->lock, flags); return err; } @@ -2061,7 +2204,6 @@ static void d40_free_chan_resources(struct dma_chan *chan) return; } - spin_lock_irqsave(&d40c->lock, flags); err = d40_free_dma(d40c); @@ -2089,61 +2231,194 @@ static struct dma_async_tx_descriptor *d40_prep_memcpy(struct dma_chan *chan, sg_dma_len(&dst_sg) = size; sg_dma_len(&src_sg) = size; - return d40_prep_sg(chan, &src_sg, &dst_sg, 1, DMA_NONE, dma_flags); + return stedma40_memcpy_sg(chan, &dst_sg, &src_sg, 1, dma_flags); } -static struct dma_async_tx_descriptor * -d40_prep_memcpy_sg(struct dma_chan *chan, - struct scatterlist *dst_sg, unsigned int dst_nents, - struct scatterlist *src_sg, unsigned int src_nents, - unsigned long dma_flags) +static dma_addr_t d40_dev_rx_addr(struct d40_chan *d40c) { - if (dst_nents != src_nents) - return NULL; + dma_addr_t dev_addr = 0; - return d40_prep_sg(chan, src_sg, dst_sg, src_nents, DMA_NONE, dma_flags); + if (d40c->runtime_addr) + dev_addr = d40c->runtime_addr; + else if (d40c->src_dev_addr) + dev_addr = d40c->src_dev_addr; + else + dev_addr = d40c->base->plat_data-> + dev_rx[d40c->dma_cfg.src_dev_type]; + + return dev_addr; } -static struct dma_async_tx_descriptor *d40_prep_slave_sg(struct dma_chan *chan, - struct scatterlist *sgl, - unsigned int sg_len, - enum dma_data_direction direction, - unsigned long dma_flags) +static dma_addr_t d40_dev_tx_addr(struct d40_chan *d40c) { - if (direction != DMA_FROM_DEVICE && direction != DMA_TO_DEVICE) - return NULL; + dma_addr_t dev_addr = 0; + + if (d40c->runtime_addr) + dev_addr = d40c->runtime_addr; + else if (d40c->dst_dev_addr) + dev_addr = d40c->dst_dev_addr; + else + dev_addr = d40c->base->plat_data-> + dev_tx[d40c->dma_cfg.dst_dev_type]; + + return dev_addr; +} + +int stedma40_set_dev_addr(struct dma_chan *chan, + dma_addr_t src_dev_addr, + dma_addr_t dst_dev_addr) +{ + struct d40_chan *d40c = container_of(chan, struct d40_chan, chan); + unsigned long flags; + + spin_lock_irqsave(&d40c->lock, flags); + + d40c->src_dev_addr = src_dev_addr; + d40c->dst_dev_addr = dst_dev_addr; + + spin_unlock_irqrestore(&d40c->lock, flags); + + return 0; +} +EXPORT_SYMBOL(stedma40_set_dev_addr); + +static int d40_prep_slave_sg_log(struct d40_desc *d40d, + struct d40_chan *d40c, + struct scatterlist *sgl, + unsigned int sg_len, + enum dma_data_direction direction, + unsigned long dma_flags) +{ + dma_addr_t dev_addr = 0; + int total_size; + + if (d40_pool_lli_alloc(d40c, d40d, sg_len, true) < 0) { + chan_err(d40c, "Out of memory\n"); + return -ENOMEM; + } + + if (direction == DMA_FROM_DEVICE) + dev_addr = d40_dev_rx_addr(d40c); + else if (direction == DMA_TO_DEVICE) + dev_addr = d40_dev_tx_addr(d40c); + else + return -EINVAL; + + total_size = d40_log_sg_to_dev(sgl, sg_len, + &d40d->lli_log, + &d40c->log_def, + d40c->dma_cfg.src_info.data_width, + d40c->dma_cfg.dst_info.data_width, + direction, + dev_addr); + if (total_size < 0) + return -EINVAL; + + return 0; +} + +static int d40_prep_slave_sg_phy(struct d40_desc *d40d, + struct d40_chan *d40c, + struct scatterlist *sgl, + unsigned int sgl_len, + enum dma_data_direction direction, + unsigned long dma_flags) +{ + dma_addr_t src_dev_addr; + dma_addr_t dst_dev_addr; + int res; + + if (d40_pool_lli_alloc(d40c, d40d, sgl_len, false) < 0) { + chan_err(d40c, "Out of memory\n"); + return -ENOMEM; + } + + if (direction == DMA_FROM_DEVICE) { + dst_dev_addr = 0; + src_dev_addr = d40_dev_rx_addr(d40c); + } else if (direction == DMA_TO_DEVICE) { + dst_dev_addr = d40_dev_tx_addr(d40c); + src_dev_addr = 0; + } else + return -EINVAL; - return d40_prep_sg(chan, sgl, sgl, sg_len, direction, dma_flags); + res = d40_phy_sg_to_lli(sgl, + sgl_len, + src_dev_addr, + d40d->lli_phy.src, + virt_to_phys(d40d->lli_phy.src), + d40c->src_def_cfg, + d40c->dma_cfg.src_info.data_width, + d40c->dma_cfg.src_info.psize, + d40d->cyclic, + d40d->txd.flags & DMA_PREP_INTERRUPT); + if (res < 0) + return res; + + res = d40_phy_sg_to_lli(sgl, + sgl_len, + dst_dev_addr, + d40d->lli_phy.dst, + virt_to_phys(d40d->lli_phy.dst), + d40c->dst_def_cfg, + d40c->dma_cfg.dst_info.data_width, + d40c->dma_cfg.dst_info.psize, + d40d->cyclic, + d40d->txd.flags & DMA_PREP_INTERRUPT); + if (res < 0) + return res; + + dma_sync_single_for_device(d40c->base->dev, d40d->lli_pool.dma_addr, + d40d->lli_pool.size, DMA_TO_DEVICE); + return 0; } static struct dma_async_tx_descriptor * -dma40_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t dma_addr, - size_t buf_len, size_t period_len, - enum dma_data_direction direction) +d40_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, + unsigned int sg_len, enum dma_data_direction direction, + unsigned long dma_flags) { - unsigned int periods = buf_len / period_len; - struct dma_async_tx_descriptor *txd; - struct scatterlist *sg; - int i; + struct d40_desc *d40d; + struct d40_chan *d40c = container_of(chan, struct d40_chan, + chan); + unsigned long flags; + int err; + - sg = kcalloc(periods + 1, sizeof(struct scatterlist), GFP_NOWAIT); - for (i = 0; i < periods; i++) { - sg_dma_address(&sg[i]) = dma_addr; - sg_dma_len(&sg[i]) = period_len; - dma_addr += period_len; + if (d40c->phy_chan == NULL) { + chan_err(d40c, "Cannot prepare unallocated channel\n"); + return ERR_PTR(-EINVAL); } - sg[periods].offset = 0; - sg[periods].length = 0; - sg[periods].page_link = - ((unsigned long)sg | 0x01) & ~0x02; + spin_lock_irqsave(&d40c->lock, flags); + d40d = d40_prep_desc(d40c, sgl, sg_len, dma_flags); + + if (d40d == NULL) + goto err; + d40_usage_inc(d40c); - txd = d40_prep_sg(chan, sg, sg, periods, direction, - DMA_PREP_INTERRUPT); + if (chan_is_logical(d40c)) + err = d40_prep_slave_sg_log(d40d, d40c, sgl, sg_len, + direction, dma_flags); + else + err = d40_prep_slave_sg_phy(d40d, d40c, sgl, sg_len, + direction, dma_flags); + d40_usage_dec(d40c); - kfree(sg); + if (err) { + chan_err(d40c, "Failed to prepare %s slave sg job: %d\n", + chan_is_logical(d40c) ? "log" : "phy", err); + goto err; + } - return txd; + spin_unlock_irqrestore(&d40c->lock, flags); + return &d40d->txd; + +err: + if (d40d) + d40_desc_free(d40c, d40d); + spin_unlock_irqrestore(&d40c->lock, flags); + return NULL; } static enum dma_status d40_tx_status(struct dma_chan *chan, @@ -2151,6 +2426,7 @@ static enum dma_status d40_tx_status(struct dma_chan *chan, struct dma_tx_state *txstate) { struct d40_chan *d40c = container_of(chan, struct d40_chan, chan); + unsigned long flags; dma_cookie_t last_used; dma_cookie_t last_complete; int ret; @@ -2168,8 +2444,14 @@ static enum dma_status d40_tx_status(struct dma_chan *chan, else ret = dma_async_is_complete(cookie, last_complete, last_used); - dma_set_tx_state(txstate, last_complete, last_used, - stedma40_residue(chan)); + if (txstate) { + txstate->last = last_complete; + txstate->used = last_used; + + spin_lock_irqsave(&d40c->lock, flags); + txstate->residue = d40_residue(d40c); + spin_unlock_irqrestore(&d40c->lock, flags); + } return ret; } @@ -2184,17 +2466,46 @@ static void d40_issue_pending(struct dma_chan *chan) return; } - spin_lock_irqsave(&d40c->lock, flags); + if (d40c->cdesc) { + stedma40_cyclic_start(chan); + return; + } - list_splice_tail_init(&d40c->pending_queue, &d40c->queue); + spin_lock_irqsave(&d40c->lock, flags); - /* Busy means that queued jobs are already being processed */ + /* Busy means that pending jobs are already being processed */ if (!d40c->busy) (void) d40_queue_start(d40c); spin_unlock_irqrestore(&d40c->lock, flags); } +static void d40_terminate_all(struct dma_chan *chan) +{ + unsigned long flags; + struct d40_chan *d40c = container_of(chan, struct d40_chan, chan); + int ret; + + spin_lock_irqsave(&d40c->lock, flags); + + d40_usage_inc(d40c); + + ret = d40_channel_execute_command(d40c, D40_DMA_STOP); + if (ret) + chan_err(d40c, "Failed to stop channel\n"); + + d40_term_all(d40c); + d40_usage_dec(d40c); + if (d40c->busy) + d40_usage_dec(d40c); + d40c->busy = false; + + spin_unlock_irqrestore(&d40c->lock, flags); + + if (d40c->cdesc) + stedma40_cyclic_free(chan); +} + static int dma40_config_to_halfchannel(struct d40_chan *d40c, struct stedma40_half_channel_info *info, @@ -2313,6 +2624,7 @@ static int d40_set_runtime_config(struct dma_chan *chan, src_addr_width = dst_addr_width; if (src_maxburst == 0) src_maxburst = dst_maxburst; + } else { dev_err(d40c->base->dev, "unrecognized channel direction %d\n", @@ -2375,11 +2687,12 @@ static int d40_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, switch (cmd) { case DMA_TERMINATE_ALL: - return d40_terminate_all(d40c); + d40_terminate_all(chan); + return 0; case DMA_PAUSE: - return d40_pause(d40c); + return d40_pause(chan); case DMA_RESUME: - return d40_resume(d40c); + return d40_resume(chan); case DMA_SLAVE_CONFIG: return d40_set_runtime_config(chan, (struct dma_slave_config *) arg); @@ -2391,6 +2704,232 @@ static int d40_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, return -ENXIO; } +dma_addr_t stedma40_get_src_addr(struct dma_chan *chan) +{ + struct d40_chan *d40c = container_of(chan, struct d40_chan, chan); + dma_addr_t addr; + + if (chan_is_physical(d40c)) + addr = readl(d40c->base->virtbase + D40_DREG_PCBASE + + d40c->phy_chan->num * D40_DREG_PCDELTA + + D40_CHAN_REG_SSPTR); + else { + unsigned long lower; + unsigned long upper; + + /* + * There is a potential for overflow between the time the two + * halves of the pointer are read. + */ + lower = d40c->lcpa->lcsp0 & D40_MEM_LCSP0_SPTR_MASK; + upper = d40c->lcpa->lcsp1 & D40_MEM_LCSP1_SPTR_MASK; + + addr = upper | lower; + } + + return addr; +} +EXPORT_SYMBOL(stedma40_get_src_addr); + +dma_addr_t stedma40_get_dst_addr(struct dma_chan *chan) +{ + struct d40_chan *d40c = container_of(chan, struct d40_chan, chan); + dma_addr_t addr; + + if (chan_is_physical(d40c)) + addr = readl(d40c->base->virtbase + D40_DREG_PCBASE + + d40c->phy_chan->num * D40_DREG_PCDELTA + + D40_CHAN_REG_SDPTR); + else { + unsigned long lower; + unsigned long upper; + + lower = d40c->lcpa->lcsp2 & D40_MEM_LCSP2_DPTR_MASK; + upper = d40c->lcpa->lcsp3 & D40_MEM_LCSP3_DPTR_MASK; + + addr = upper | lower; + } + + return addr; +} +EXPORT_SYMBOL(stedma40_get_dst_addr); + +int stedma40_cyclic_start(struct dma_chan *chan) +{ + struct d40_chan *d40c = container_of(chan, struct d40_chan, chan); + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&d40c->lock, flags); + + if (!d40c->cdesc) + goto out; + + d40_usage_inc(d40c); + + ret = d40_start(d40c); + if (!ret) + d40c->busy = true; + else + d40_usage_dec(d40c); + +out: + spin_unlock_irqrestore(&d40c->lock, flags); + return ret; +} +EXPORT_SYMBOL(stedma40_cyclic_start); + +void stedma40_cyclic_stop(struct dma_chan *chan) +{ + d40_terminate_all(chan); +} +EXPORT_SYMBOL(stedma40_cyclic_stop); + +void stedma40_cyclic_free(struct dma_chan *chan) +{ + struct d40_chan *d40c = container_of(chan, struct d40_chan, chan); + struct stedma40_cyclic_desc *cdesc; + unsigned long flags; + + spin_lock_irqsave(&d40c->lock, flags); + + cdesc = d40c->cdesc; + if (!cdesc) { + spin_unlock_irqrestore(&d40c->lock, flags); + return; + } + + d40c->cdesc = NULL; + d40_lcla_free_all(d40c, cdesc->d40d); + + spin_unlock_irqrestore(&d40c->lock, flags); + + kfree(cdesc); +} +EXPORT_SYMBOL(stedma40_cyclic_free); + +struct stedma40_cyclic_desc * +stedma40_cyclic_prep_sg(struct dma_chan *chan, + struct scatterlist *sgl, + unsigned int sg_len, + enum dma_data_direction direction, + unsigned long dma_flags) +{ + struct d40_chan *d40c = container_of(chan, struct d40_chan, chan); + struct stedma40_cyclic_desc *cdesc; + struct d40_desc *d40d; + unsigned long flags; + void *mem; + int err; + + mem = kzalloc(sizeof(struct stedma40_cyclic_desc) + + sizeof(struct d40_desc), GFP_ATOMIC); + if (!mem) + return ERR_PTR(-ENOMEM); + + cdesc = mem; + d40d = cdesc->d40d = mem + sizeof(struct stedma40_cyclic_desc); + + spin_lock_irqsave(&d40c->lock, flags); + + if (d40c->phy_chan == NULL) { + chan_err(d40c, "Cannot prepare unallocated channel\n"); + err = -EINVAL; + goto out; + } + + if (d40c->cdesc || d40c->busy) { + chan_err(d40c, "Cannot prepare cyclic job for busy channel\n"); + err = -EBUSY; + goto out; + } + + d40d->cyclic = true; + d40d->txd.flags = dma_flags; + INIT_LIST_HEAD(&d40d->node); + + d40_usage_inc(d40c); + + if (chan_is_logical(d40c)) + err = d40_prep_slave_sg_log(d40d, d40c, sgl, sg_len, + direction, dma_flags); + else + err = d40_prep_slave_sg_phy(d40d, d40c, sgl, sg_len, + direction, dma_flags); + + if (err) { + chan_err(d40c,"Failed to prepare %s slave sg job: %d\n", + chan_is_logical(d40c) ? "log" : "phy", err); + goto out2; + } + + d40d->lli_len = sg_len; + d40d->lli_current = 0; + + d40_desc_load(d40c, d40d); + + /* + * Couldn't get enough LCLA. We don't support splitting of cyclic + * jobs. + */ + if (d40d->lli_current != d40d->lli_len) { + chan_err(d40c,"Couldn't prepare cyclic job: not enough LCLA"); + err = -EBUSY; + goto out2; + } + + d40c->cdesc = cdesc; + d40_usage_dec(d40c); + spin_unlock_irqrestore(&d40c->lock, flags); + return cdesc; +out2: + d40_usage_dec(d40c); +out: + if (d40c->phy_chan) + d40_lcla_free_all(d40c, cdesc->d40d); + kfree(cdesc); + spin_unlock_irqrestore(&d40c->lock, flags); + return ERR_PTR(err); +} +EXPORT_SYMBOL(stedma40_cyclic_prep_sg); + +struct dma_async_tx_descriptor * +dma40_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t buf_addr, + size_t buf_len, size_t period_len, + enum dma_data_direction direction) +{ + unsigned int sg_len = buf_len / period_len; + struct stedma40_cyclic_desc *cdesc; + struct dma_async_tx_descriptor *txd; + struct scatterlist *sg; + int i; + + sg = kzalloc(sizeof(struct scatterlist) * sg_len, GFP_ATOMIC); + if (!sg) + return ERR_PTR(-ENOMEM); + + sg_init_table(sg, sg_len); + for (i = 0; i < sg_len; i++) { + sg_dma_address(&sg[i]) = buf_addr + i * period_len; + sg_dma_len(&sg[i]) = period_len; + } + + cdesc = stedma40_cyclic_prep_sg(chan, sg, sg_len, direction, + DMA_PREP_INTERRUPT); + kfree(sg); + + if (IS_ERR(cdesc)) + return ERR_PTR(PTR_ERR(cdesc)); + + txd = &cdesc->d40d->txd; + + txd->flags = DMA_PREP_INTERRUPT; + dma_async_tx_descriptor_init(txd, chan); + txd->tx_submit = d40_tx_submit; + + return txd; +} + /* Initialization functions */ static void __init d40_chan_init(struct d40_base *base, struct dma_device *dma, @@ -2411,11 +2950,10 @@ static void __init d40_chan_init(struct d40_base *base, struct dma_device *dma, d40c->log_num = D40_PHY_CHAN; + INIT_LIST_HEAD(&d40c->done); INIT_LIST_HEAD(&d40c->active); INIT_LIST_HEAD(&d40c->queue); - INIT_LIST_HEAD(&d40c->pending_queue); INIT_LIST_HEAD(&d40c->client); - INIT_LIST_HEAD(&d40c->prepare_queue); tasklet_init(&d40c->tasklet, dma_tasklet, (unsigned long) d40c); @@ -2440,9 +2978,6 @@ static void d40_ops_init(struct d40_base *base, struct dma_device *dev) dev->copy_align = 2; } - if (dma_has_cap(DMA_SG, dev->cap_mask)) - dev->device_prep_dma_sg = d40_prep_memcpy_sg; - if (dma_has_cap(DMA_CYCLIC, dev->cap_mask)) dev->device_prep_dma_cyclic = dma40_prep_dma_cyclic; @@ -2480,7 +3015,6 @@ static int __init d40_dmaengine_init(struct d40_base *base, dma_cap_zero(base->dma_memcpy.cap_mask); dma_cap_set(DMA_MEMCPY, base->dma_memcpy.cap_mask); - dma_cap_set(DMA_SG, base->dma_memcpy.cap_mask); d40_ops_init(base, &base->dma_memcpy); @@ -2498,10 +3032,10 @@ static int __init d40_dmaengine_init(struct d40_base *base, dma_cap_zero(base->dma_both.cap_mask); dma_cap_set(DMA_SLAVE, base->dma_both.cap_mask); dma_cap_set(DMA_MEMCPY, base->dma_both.cap_mask); - dma_cap_set(DMA_SG, base->dma_both.cap_mask); - dma_cap_set(DMA_CYCLIC, base->dma_slave.cap_mask); + dma_cap_set(DMA_CYCLIC, base->dma_both.cap_mask); d40_ops_init(base, &base->dma_both); + err = dma_async_device_register(&base->dma_both); if (err) { @@ -2518,6 +3052,72 @@ failure1: return err; } +/* Suspend resume functionality */ +#ifdef CONFIG_PM +static int dma40_pm_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct d40_base *base = platform_get_drvdata(pdev); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&base->usage_lock, flags); + + if (base->usage) + ret = -EBUSY; + + spin_unlock_irqrestore(&base->usage_lock, flags); + + if (base->lcpa_regulator) + ret = regulator_disable(base->lcpa_regulator); + + return ret; +} + +static int dma40_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct d40_base *base = platform_get_drvdata(pdev); + + d40_save_restore_registers(base, true); + + return 0; +} + +static int dma40_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct d40_base *base = platform_get_drvdata(pdev); + + if (base->initialized) + d40_save_restore_registers(base, false); + + return 0; +} + +static int dma40_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct d40_base *base = platform_get_drvdata(pdev); + int ret = 0; + + if (base->lcpa_regulator) + ret = regulator_enable(base->lcpa_regulator); + + return ret; +} + +static const struct dev_pm_ops dma40_pm_ops = { + .suspend = dma40_pm_suspend, + .runtime_suspend = dma40_runtime_suspend, + .runtime_resume = dma40_runtime_resume, + .resume = dma40_resume, +}; +#define DMA40_PM_OPS (&dma40_pm_ops) +#else +#define DMA40_PM_OPS NULL +#endif + /* Initialization functions. */ static int __init d40_phy_res_init(struct d40_base *base) @@ -2525,6 +3125,7 @@ static int __init d40_phy_res_init(struct d40_base *base) int i; int num_phy_chans_avail = 0; u32 val[2]; + u32 gcc = D40_DREG_GCC_ENA; int odd_even_bit = -2; val[0] = readl(base->virtbase + D40_DREG_PRSME); @@ -2533,13 +3134,22 @@ static int __init d40_phy_res_init(struct d40_base *base) for (i = 0; i < base->num_phy_chans; i++) { base->phy_res[i].num = i; odd_even_bit += 2 * ((i % 2) == 0); - if (((val[i % 2] >> odd_even_bit) & 3) == 1) { + if (((val[i % 2] >> odd_even_bit) & D40_DREG_PRSM_MODE_MASK) + == D40_DREG_PRSM_MODE_SECURE) { /* Mark security only channels as occupied */ base->phy_res[i].allocated_src = D40_ALLOC_PHY; base->phy_res[i].allocated_dst = D40_ALLOC_PHY; + base->phy_res[i].reserved = true; + + gcc |= D40_DREG_GCC_EVTGRP_ENA(D40_PHYS_TO_GROUP(i), + D40_DREG_GCC_DST); + gcc |= D40_DREG_GCC_EVTGRP_ENA(D40_PHYS_TO_GROUP(i), + D40_DREG_GCC_SRC); + } else { base->phy_res[i].allocated_src = D40_ALLOC_FREE; base->phy_res[i].allocated_dst = D40_ALLOC_FREE; + base->phy_res[i].reserved = false; num_phy_chans_avail++; } spin_lock_init(&base->phy_res[i].lock); @@ -2551,6 +3161,7 @@ static int __init d40_phy_res_init(struct d40_base *base) base->phy_res[chan].allocated_src = D40_ALLOC_PHY; base->phy_res[chan].allocated_dst = D40_ALLOC_PHY; + base->phy_res[chan].reserved = true; num_phy_chans_avail--; } @@ -2571,11 +3182,33 @@ static int __init d40_phy_res_init(struct d40_base *base) val[0] = val[0] >> 2; } + /* Enable all clocks -- revisit after HW bug is fixed */ + writel(D40_DREG_GCC_ENABLE_ALL, base->virtbase + D40_DREG_GCC); + return num_phy_chans_avail; } static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) { + static const struct d40_reg_val dma_id_regs[] = { + /* Peripheral Id */ + { .reg = D40_DREG_PERIPHID0, .val = 0x0040}, + { .reg = D40_DREG_PERIPHID1, .val = 0x0000}, + /* + * D40_DREG_PERIPHID2 Depends on HW revision: + * DB8500ed has 0x0008, + * ? has 0x0018, + * DB8500v1 has 0x0028 + * DB8500v2 has 0x0038 + */ + { .reg = D40_DREG_PERIPHID3, .val = 0x0000}, + + /* PCell Id */ + { .reg = D40_DREG_CELLID0, .val = 0x000d}, + { .reg = D40_DREG_CELLID1, .val = 0x00f0}, + { .reg = D40_DREG_CELLID2, .val = 0x0005}, + { .reg = D40_DREG_CELLID3, .val = 0x00b1} + }; struct stedma40_platform_data *plat_data; struct clk *clk = NULL; void __iomem *virtbase = NULL; @@ -2584,9 +3217,7 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) int num_log_chans = 0; int num_phy_chans; int i; - u32 pid; - u32 cid; - u8 rev; + u32 val; clk = clk_get(&pdev->dev, NULL); @@ -2595,6 +3226,10 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) goto failure; } + /* + * Since the secure world does not handle clock, we have to + * let it run all the time + */ clk_enable(clk); /* Get IO for DMAC base address */ @@ -2610,38 +3245,33 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) if (!virtbase) goto failure; - /* This is just a regular AMBA PrimeCell ID actually */ - for (pid = 0, i = 0; i < 4; i++) - pid |= (readl(virtbase + resource_size(res) - 0x20 + 4 * i) - & 255) << (i * 8); - for (cid = 0, i = 0; i < 4; i++) - cid |= (readl(virtbase + resource_size(res) - 0x10 + 4 * i) - & 255) << (i * 8); - - if (cid != AMBA_CID) { - d40_err(&pdev->dev, "Unknown hardware! No PrimeCell ID\n"); - goto failure; + /* HW version check */ + for (i = 0; i < ARRAY_SIZE(dma_id_regs); i++) { + if (dma_id_regs[i].val != + readl(virtbase + dma_id_regs[i].reg)) { + d40_err(&pdev->dev, + "Unknown hardware! Expected 0x%x at 0x%x but got 0x%x\n", + dma_id_regs[i].val, + dma_id_regs[i].reg, + readl(virtbase + dma_id_regs[i].reg)); + goto failure; + } } - if (AMBA_MANF_BITS(pid) != AMBA_VENDOR_ST) { + + /* Get silicon revision */ + val = readl(virtbase + D40_DREG_PERIPHID2); + + if ((val & 0xf) != D40_PERIPHID2_DESIGNER) { d40_err(&pdev->dev, "Unknown designer! Got %x wanted %x\n", - AMBA_MANF_BITS(pid), - AMBA_VENDOR_ST); + val & 0xf, D40_PERIPHID2_DESIGNER); goto failure; } - /* - * HW revision: - * DB8500ed has revision 0 - * ? has revision 1 - * DB8500v1 has revision 2 - * DB8500v2 has revision 3 - */ - rev = AMBA_REV_BITS(pid); /* The number of physical channels on this HW */ num_phy_chans = 4 * (readl(virtbase + D40_DREG_ICFG) & 0x7) + 4; dev_info(&pdev->dev, "hardware revision: %d @ 0x%x\n", - rev, res->start); + (val >> 4) & 0xf, res->start); plat_data = pdev->dev.platform_data; @@ -2663,7 +3293,7 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) goto failure; } - base->rev = rev; + base->rev = (val >> 4) & 0xf; base->clk = clk; base->num_phy_chans = num_phy_chans; base->num_log_chans = num_log_chans; @@ -2674,6 +3304,7 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) base->dev = &pdev->dev; base->phy_chans = ((void *)base) + ALIGN(sizeof(struct d40_base), 4); base->log_chans = &base->phy_chans[num_phy_chans]; + base->usage = 1; base->phy_res = kzalloc(num_phy_chans * sizeof(struct d40_phy_res), GFP_KERNEL); @@ -2698,10 +3329,15 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) goto failure; } - base->lcla_pool.alloc_map = kzalloc(num_phy_chans * - sizeof(struct d40_desc *) * - D40_LCLA_LINK_PER_EVENT_GRP, + base->reg_val_backup_chan = kmalloc(base->num_phy_chans * + sizeof(d40_backup_regs_chan), GFP_KERNEL); + if (!base->reg_val_backup_chan) + goto failure; + + base->lcla_pool.alloc_map = + kzalloc(num_phy_chans * sizeof(struct d40_desc *) + * D40_LCLA_LINK_PER_EVENT_GRP, GFP_KERNEL); if (!base->lcla_pool.alloc_map) goto failure; @@ -2728,6 +3364,7 @@ failure: if (base) { kfree(base->lcla_pool.alloc_map); + kfree(base->reg_val_backup_chan); kfree(base->lookup_log_chans); kfree(base->lookup_phy_chans); kfree(base->phy_res); @@ -2740,9 +3377,9 @@ failure: static void __init d40_hw_init(struct d40_base *base) { - static const struct d40_reg_val dma_init_reg[] = { + static struct d40_reg_val dma_init_reg[] = { /* Clock every part of the DMA block from start */ - { .reg = D40_DREG_GCC, .val = 0x0000ff01}, + { .reg = D40_DREG_GCC, .val = D40_DREG_GCC_ENABLE_ALL}, /* Interrupts on all logical channels */ { .reg = D40_DREG_LCMIS0, .val = 0xFFFFFFFF}, @@ -2808,7 +3445,8 @@ static int __init d40_lcla_allocate(struct d40_base *base) { struct d40_lcla_pool *pool = &base->lcla_pool; unsigned long *page_list; - int i, j; + int i; + int j; int ret = 0; /* @@ -2852,8 +3490,8 @@ static int __init d40_lcla_allocate(struct d40_base *base) base->lcla_pool.base = (void *)page_list[i]; } else { /* - * After many attempts and no succees with finding the correct - * alignment, try with allocating a big buffer. + * After many attempts, no succees with finding the correct + * alignment try with allocating a big buffer. */ dev_warn(base->dev, "[%s] Failed to get %d pages @ 18 bit align.\n", @@ -2895,6 +3533,7 @@ static int __init d40_probe(struct platform_device *pdev) struct resource *res = NULL; int num_reserved_chans; u32 val; + unsigned long flags; base = d40_hw_detect_init(pdev); @@ -2907,6 +3546,7 @@ static int __init d40_probe(struct platform_device *pdev) spin_lock_init(&base->interrupt_lock); spin_lock_init(&base->execmd_lock); + spin_lock_init(&base->usage_lock); /* Get IO for logical channel parameter address */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lcpa"); @@ -2942,13 +3582,40 @@ static int __init d40_probe(struct platform_device *pdev) d40_err(&pdev->dev, "Failed to ioremap LCPA region\n"); goto failure; } + /* If lcla has to be located in ESRAM we don't need to allocate */ + if (base->plat_data->use_esram_lcla) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "lcla_esram"); + if (!res) { + ret = -ENOENT; + d40_err(&pdev->dev, + "No \"lcla_esram\" memory resource\n"); + goto failure; + } + base->lcla_pool.base = ioremap(res->start, + resource_size(res)); + if (!base->lcla_pool.base) { + ret = -ENOMEM; + d40_err(&pdev->dev,"Failed to ioremap LCLA region\n"); + goto failure; + } + writel(res->start, base->virtbase + D40_DREG_LCLA); - ret = d40_lcla_allocate(base); - if (ret) { - d40_err(&pdev->dev, "Failed to allocate LCLA area\n"); - goto failure; + } else { + ret = d40_lcla_allocate(base); + if (ret) { + d40_err(&pdev->dev, "Failed to allocate LCLA area\n"); + goto failure; + } } +#ifdef CONFIG_STE_DMA40_DEBUG + sted40_history_set_virtbase(base->virtbase, + base->lcpa_base, + base->lcpa_size, + base->lcla_pool.base, + SZ_1K * base->num_phy_chans); +#endif spin_lock_init(&base->lcla_pool.lock); base->irq = platform_get_irq(pdev, 0); @@ -2959,12 +3626,43 @@ static int __init d40_probe(struct platform_device *pdev) goto failure; } + pm_runtime_irq_safe(base->dev); + pm_runtime_set_autosuspend_delay(base->dev, DMA40_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(base->dev); + pm_runtime_enable(base->dev); + pm_runtime_resume(base->dev); + + if (base->plat_data->use_esram_lcla) { + + base->lcpa_regulator = regulator_get(base->dev, "lcla_esram"); + if (IS_ERR(base->lcpa_regulator)) { + d40_err(&pdev->dev, "Failed to get lcpa_regulator\n"); + base->lcpa_regulator = NULL; + goto failure; + } + + ret = regulator_enable(base->lcpa_regulator); + if (ret) { + d40_err(&pdev->dev, + "Failed to enable lcpa_regulator\n"); + regulator_put(base->lcpa_regulator); + base->lcpa_regulator = NULL; + goto failure; + } + } + + base->initialized = true; + err = d40_dmaengine_init(base, num_reserved_chans); if (err) goto failure; d40_hw_init(base); + spin_lock_irqsave(&base->usage_lock, flags); + base->usage--; + spin_unlock_irqrestore(&base->usage_lock, flags); + dev_info(base->dev, "initialized\n"); return 0; @@ -2975,6 +3673,11 @@ failure: if (base->virtbase) iounmap(base->virtbase); + if (base->lcla_pool.base && base->plat_data->use_esram_lcla) { + iounmap(base->lcla_pool.base); + base->lcla_pool.base = NULL; + } + if (base->lcla_pool.dma_addr) dma_unmap_single(base->dev, base->lcla_pool.dma_addr, SZ_1K * base->num_phy_chans, @@ -2985,7 +3688,6 @@ failure: base->lcla_pool.pages); kfree(base->lcla_pool.base_unaligned); - if (base->phy_lcpa) release_mem_region(base->phy_lcpa, base->lcpa_size); @@ -2997,6 +3699,11 @@ failure: clk_put(base->clk); } + if (base->lcpa_regulator) { + regulator_disable(base->lcpa_regulator); + regulator_put(base->lcpa_regulator); + } + kfree(base->lcla_pool.alloc_map); kfree(base->lookup_log_chans); kfree(base->lookup_phy_chans); @@ -3012,6 +3719,7 @@ static struct platform_driver d40_driver = { .driver = { .owner = THIS_MODULE, .name = D40_NAME, + .pm = DMA40_PM_OPS, }, }; diff --git a/drivers/dma/ste_dma40_ll.c b/drivers/dma/ste_dma40_ll.c index cad9e1daedf..017df1d2824 100644 --- a/drivers/dma/ste_dma40_ll.c +++ b/drivers/dma/ste_dma40_ll.c @@ -1,6 +1,6 @@ /* * Copyright (C) ST-Ericsson SA 2007-2010 - * Author: Per Forlin <per.forlin@stericsson.com> for ST-Ericsson + * Author: Per Friden <per.friden@stericsson.com> for ST-Ericsson * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson * License terms: GNU General Public License (GPL) version 2 */ @@ -9,6 +9,12 @@ #include <plat/ste_dma40.h> #include "ste_dma40_ll.h" +#ifdef CONFIG_STE_DMA40_DEBUG +#include "ste_dma40_debug.h" +#define MARK sted40_history_text((char *)__func__) +#else +#define MARK +#endif /* Sets up proper LCSP1 and LCSP3 register for a logical channel */ void d40_log_cfg(struct stedma40_chan_cfg *cfg, @@ -102,16 +108,18 @@ void d40_phy_cfg(struct stedma40_chan_cfg *cfg, src |= cfg->src_info.data_width << D40_SREG_CFG_ESIZE_POS; dst |= cfg->dst_info.data_width << D40_SREG_CFG_ESIZE_POS; + /* Set the priority bit to high for the physical channel */ + if (cfg->high_priority) { + src |= 1 << D40_SREG_CFG_PRI_POS; + dst |= 1 << D40_SREG_CFG_PRI_POS; + } + } else { /* Logical channel */ dst |= 1 << D40_SREG_CFG_LOG_GIM_POS; src |= 1 << D40_SREG_CFG_LOG_GIM_POS; } - if (cfg->high_priority) { - src |= 1 << D40_SREG_CFG_PRI_POS; - dst |= 1 << D40_SREG_CFG_PRI_POS; - } if (cfg->src_info.big_endian) src |= 1 << D40_SREG_CFG_LBE_POS; @@ -122,18 +130,16 @@ void d40_phy_cfg(struct stedma40_chan_cfg *cfg, *dst_cfg = dst; } -static int d40_phy_fill_lli(struct d40_phy_lli *lli, - dma_addr_t data, - u32 data_size, - dma_addr_t next_lli, - u32 reg_cfg, - struct stedma40_half_channel_info *info, - unsigned int flags) +int d40_phy_fill_lli(struct d40_phy_lli *lli, + dma_addr_t data, + u32 data_size, + int psize, + dma_addr_t next_lli, + u32 reg_cfg, + bool term_int, + u32 data_width, + bool is_device) { - bool addr_inc = flags & LLI_ADDR_INC; - bool term_int = flags & LLI_TERM_INT; - unsigned int data_width = info->data_width; - int psize = info->psize; int num_elems; if (psize == STEDMA40_PSIZE_PHY_1) @@ -141,6 +147,13 @@ static int d40_phy_fill_lli(struct d40_phy_lli *lli, else num_elems = 2 << psize; + /* + * Size is 16bit. data_width is 8, 16, 32 or 64 bit + * Block large than 64 KiB must be split. + */ + if (data_size > (0xffff << data_width)) + return -EINVAL; + /* Must be aligned */ if (!IS_ALIGNED(data, 0x1 << data_width)) return -EINVAL; @@ -156,7 +169,7 @@ static int d40_phy_fill_lli(struct d40_phy_lli *lli, * Distance to next element sized entry. * Usually the size of the element unless you want gaps. */ - if (addr_inc) + if (!is_device) lli->reg_elt |= (0x1 << data_width) << D40_SREG_ELEM_PHY_EIDX_POS; @@ -182,128 +195,96 @@ static int d40_phy_fill_lli(struct d40_phy_lli *lli, return 0; } -static int d40_seg_size(int size, int data_width1, int data_width2) -{ - u32 max_w = max(data_width1, data_width2); - u32 min_w = min(data_width1, data_width2); - u32 seg_max = ALIGN(STEDMA40_MAX_SEG_SIZE << min_w, 1 << max_w); - - if (seg_max > STEDMA40_MAX_SEG_SIZE) - seg_max -= (1 << max_w); - - if (size <= seg_max) - return size; - - if (size <= 2 * seg_max) - return ALIGN(size / 2, 1 << max_w); - - return seg_max; -} - -static struct d40_phy_lli * -d40_phy_buf_to_lli(struct d40_phy_lli *lli, dma_addr_t addr, u32 size, - dma_addr_t lli_phys, dma_addr_t first_phys, u32 reg_cfg, - struct stedma40_half_channel_info *info, - struct stedma40_half_channel_info *otherinfo, - unsigned long flags) -{ - bool lastlink = flags & LLI_LAST_LINK; - bool addr_inc = flags & LLI_ADDR_INC; - bool term_int = flags & LLI_TERM_INT; - bool cyclic = flags & LLI_CYCLIC; - int err; - dma_addr_t next = lli_phys; - int size_rest = size; - int size_seg = 0; - - /* - * This piece may be split up based on d40_seg_size(); we only want the - * term int on the last part. - */ - if (term_int) - flags &= ~LLI_TERM_INT; - - do { - size_seg = d40_seg_size(size_rest, info->data_width, - otherinfo->data_width); - size_rest -= size_seg; - - if (size_rest == 0 && term_int) - flags |= LLI_TERM_INT; - - if (size_rest == 0 && lastlink) - next = cyclic ? first_phys : 0; - else - next = ALIGN(next + sizeof(struct d40_phy_lli), - D40_LLI_ALIGN); - - err = d40_phy_fill_lli(lli, addr, size_seg, next, - reg_cfg, info, flags); - - if (err) - goto err; - - lli++; - if (addr_inc) - addr += size_seg; - } while (size_rest); - - return lli; - - err: - return NULL; -} - int d40_phy_sg_to_lli(struct scatterlist *sg, int sg_len, dma_addr_t target, - struct d40_phy_lli *lli_sg, + struct d40_phy_lli *lli, dma_addr_t lli_phys, u32 reg_cfg, - struct stedma40_half_channel_info *info, - struct stedma40_half_channel_info *otherinfo, - unsigned long flags) + u32 data_width, + int psize, + bool cyclic, + bool cyclic_int) { int total_size = 0; int i; struct scatterlist *current_sg = sg; - struct d40_phy_lli *lli = lli_sg; - dma_addr_t l_phys = lli_phys; - - if (!target) - flags |= LLI_ADDR_INC; + dma_addr_t next_lli_phys; + dma_addr_t dst; + bool interrupt; + int err = 0; for_each_sg(sg, current_sg, sg_len, i) { - dma_addr_t sg_addr = sg_dma_address(current_sg); - unsigned int len = sg_dma_len(current_sg); - dma_addr_t dst = target ?: sg_addr; total_size += sg_dma_len(current_sg); - if (i == sg_len - 1) - flags |= LLI_TERM_INT | LLI_LAST_LINK; - - l_phys = ALIGN(lli_phys + (lli - lli_sg) * - sizeof(struct d40_phy_lli), D40_LLI_ALIGN); + /* If this scatter list entry is the last one, no next link */ + if (sg_len - 1 == i) + next_lli_phys = cyclic ? lli_phys : 0; + else + next_lli_phys = ALIGN(lli_phys + (i + 1) * + sizeof(struct d40_phy_lli), + D40_LLI_ALIGN); - lli = d40_phy_buf_to_lli(lli, dst, len, l_phys, lli_phys, - reg_cfg, info, otherinfo, flags); + interrupt = cyclic ? cyclic_int : !next_lli_phys; - if (lli == NULL) - return -EINVAL; + if (target) + dst = target; + else + dst = sg_dma_address(current_sg); + + err = d40_phy_fill_lli(&lli[i], + dst, + sg_dma_len(current_sg), + psize, + next_lli_phys, + reg_cfg, + interrupt, + data_width, + target == dst); + if (err) + goto err; } return total_size; +err: + return err; } +void d40_phy_lli_write(void __iomem *virtbase, + u32 phy_chan_num, + struct d40_phy_lli *lli_dst, + struct d40_phy_lli *lli_src) +{ + + writel(lli_src->reg_cfg, virtbase + D40_DREG_PCBASE + + phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SSCFG); + writel(lli_src->reg_elt, virtbase + D40_DREG_PCBASE + + phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SSELT); + writel(lli_src->reg_ptr, virtbase + D40_DREG_PCBASE + + phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SSPTR); + writel(lli_src->reg_lnk, virtbase + D40_DREG_PCBASE + + phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SSLNK); + + writel(lli_dst->reg_cfg, virtbase + D40_DREG_PCBASE + + phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SDCFG); + writel(lli_dst->reg_elt, virtbase + D40_DREG_PCBASE + + phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SDELT); + writel(lli_dst->reg_ptr, virtbase + D40_DREG_PCBASE + + phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SDPTR); + writel(lli_dst->reg_lnk, virtbase + D40_DREG_PCBASE + + phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SDLNK); + +} + /* DMA logical lli operations */ static void d40_log_lli_link(struct d40_log_lli *lli_dst, struct d40_log_lli *lli_src, - int next, unsigned int flags) + int next, + bool interrupt) { - bool interrupt = flags & LLI_TERM_INT; u32 slos = 0; u32 dlos = 0; @@ -327,45 +308,42 @@ static void d40_log_lli_link(struct d40_log_lli *lli_dst, void d40_log_lli_lcpa_write(struct d40_log_lli_full *lcpa, struct d40_log_lli *lli_dst, struct d40_log_lli *lli_src, - int next, unsigned int flags) + int next, + bool interrupt) { - d40_log_lli_link(lli_dst, lli_src, next, flags); + d40_log_lli_link(lli_dst, lli_src, next, interrupt); - writel(lli_src->lcsp02, &lcpa[0].lcsp0); - writel(lli_src->lcsp13, &lcpa[0].lcsp1); - writel(lli_dst->lcsp02, &lcpa[0].lcsp2); - writel(lli_dst->lcsp13, &lcpa[0].lcsp3); + writel_relaxed(lli_src->lcsp02, &lcpa[0].lcsp0); + writel_relaxed(lli_src->lcsp13, &lcpa[0].lcsp1); + writel_relaxed(lli_dst->lcsp02, &lcpa[0].lcsp2); + writel_relaxed(lli_dst->lcsp13, &lcpa[0].lcsp3); } void d40_log_lli_lcla_write(struct d40_log_lli *lcla, struct d40_log_lli *lli_dst, struct d40_log_lli *lli_src, - int next, unsigned int flags) + int next, + bool interrupt) { - d40_log_lli_link(lli_dst, lli_src, next, flags); + d40_log_lli_link(lli_dst, lli_src, next, interrupt); - writel(lli_src->lcsp02, &lcla[0].lcsp02); - writel(lli_src->lcsp13, &lcla[0].lcsp13); - writel(lli_dst->lcsp02, &lcla[1].lcsp02); - writel(lli_dst->lcsp13, &lcla[1].lcsp13); + writel_relaxed(lli_src->lcsp02, &lcla[0].lcsp02); + writel_relaxed(lli_src->lcsp13, &lcla[0].lcsp13); + writel_relaxed(lli_dst->lcsp02, &lcla[1].lcsp02); + writel_relaxed(lli_dst->lcsp13, &lcla[1].lcsp13); } -static void d40_log_fill_lli(struct d40_log_lli *lli, - dma_addr_t data, u32 data_size, - u32 reg_cfg, - u32 data_width, - unsigned int flags) +void d40_log_fill_lli(struct d40_log_lli *lli, + dma_addr_t data, u32 data_size, + u32 reg_cfg, + u32 data_width, + bool addr_inc) { - bool addr_inc = flags & LLI_ADDR_INC; - lli->lcsp13 = reg_cfg; /* The number of elements to transfer */ lli->lcsp02 = ((data_size >> data_width) << D40_MEM_LCSP0_ECNT_POS) & D40_MEM_LCSP0_ECNT_MASK; - - BUG_ON((data_size >> data_width) > STEDMA40_MAX_SEG_SIZE); - /* 16 LSBs address of the current element */ lli->lcsp02 |= data & D40_MEM_LCSP0_SPTR_MASK; /* 16 MSBs address of the current element */ @@ -376,65 +354,67 @@ static void d40_log_fill_lli(struct d40_log_lli *lli, } -static struct d40_log_lli *d40_log_buf_to_lli(struct d40_log_lli *lli_sg, - dma_addr_t addr, - int size, - u32 lcsp13, /* src or dst*/ - u32 data_width1, - u32 data_width2, - unsigned int flags) +int d40_log_sg_to_dev(struct scatterlist *sg, + int sg_len, + struct d40_log_lli_bidir *lli, + struct d40_def_lcsp *lcsp, + u32 src_data_width, + u32 dst_data_width, + enum dma_data_direction direction, + dma_addr_t dev_addr) { - bool addr_inc = flags & LLI_ADDR_INC; - struct d40_log_lli *lli = lli_sg; - int size_rest = size; - int size_seg = 0; - - do { - size_seg = d40_seg_size(size_rest, data_width1, data_width2); - size_rest -= size_seg; - - d40_log_fill_lli(lli, - addr, - size_seg, - lcsp13, data_width1, - flags); - if (addr_inc) - addr += size_seg; - lli++; - } while (size_rest); - - return lli; + int total_size = 0; + struct scatterlist *current_sg = sg; + int i; + + for_each_sg(sg, current_sg, sg_len, i) { + total_size += sg_dma_len(current_sg); + + if (direction == DMA_TO_DEVICE) { + d40_log_fill_lli(&lli->src[i], + sg_dma_address(current_sg), + sg_dma_len(current_sg), + lcsp->lcsp1, src_data_width, + true); + d40_log_fill_lli(&lli->dst[i], + dev_addr, + sg_dma_len(current_sg), + lcsp->lcsp3, dst_data_width, + false); + } else { + d40_log_fill_lli(&lli->dst[i], + sg_dma_address(current_sg), + sg_dma_len(current_sg), + lcsp->lcsp3, dst_data_width, + true); + d40_log_fill_lli(&lli->src[i], + dev_addr, + sg_dma_len(current_sg), + lcsp->lcsp1, src_data_width, + false); + } + } + return total_size; } int d40_log_sg_to_lli(struct scatterlist *sg, int sg_len, - dma_addr_t dev_addr, struct d40_log_lli *lli_sg, u32 lcsp13, /* src or dst*/ - u32 data_width1, u32 data_width2) + u32 data_width) { int total_size = 0; struct scatterlist *current_sg = sg; int i; - struct d40_log_lli *lli = lli_sg; - unsigned long flags = 0; - - if (!dev_addr) - flags |= LLI_ADDR_INC; for_each_sg(sg, current_sg, sg_len, i) { - dma_addr_t sg_addr = sg_dma_address(current_sg); - unsigned int len = sg_dma_len(current_sg); - dma_addr_t addr = dev_addr ?: sg_addr; - total_size += sg_dma_len(current_sg); - lli = d40_log_buf_to_lli(lli, addr, len, - lcsp13, - data_width1, - data_width2, - flags); + d40_log_fill_lli(&lli_sg[i], + sg_dma_address(current_sg), + sg_dma_len(current_sg), + lcsp13, data_width, + true); } - return total_size; } diff --git a/drivers/dma/ste_dma40_ll.h b/drivers/dma/ste_dma40_ll.h index b44c455158d..22b5fd76f96 100644 --- a/drivers/dma/ste_dma40_ll.h +++ b/drivers/dma/ste_dma40_ll.h @@ -16,6 +16,8 @@ #define D40_TYPE_TO_GROUP(type) (type / 16) #define D40_TYPE_TO_EVENT(type) (type % 16) +#define D40_GROUP_SIZE 8 +#define D40_PHYS_TO_GROUP(phys) ((phys & (D40_GROUP_SIZE - 1)) / 2) /* Most bits of the CFG register are the same in log as in phy mode */ #define D40_SREG_CFG_MST_POS 15 @@ -94,10 +96,13 @@ /* LCSP2 */ #define D40_MEM_LCSP2_ECNT_POS 16 +#define D40_MEM_LCSP2_DPTR_POS 0 #define D40_MEM_LCSP2_ECNT_MASK (0xFFFF << D40_MEM_LCSP2_ECNT_POS) +#define D40_MEM_LCSP2_DPTR_MASK (0xFFFF << D40_MEM_LCSP2_DPTR_POS) /* LCSP3 */ +#define D40_MEM_LCSP3_DPTR_POS 16 #define D40_MEM_LCSP3_DCFG_MST_POS 15 #define D40_MEM_LCSP3_DCFG_TIM_POS 14 #define D40_MEM_LCSP3_DCFG_EIM_POS 13 @@ -107,6 +112,7 @@ #define D40_MEM_LCSP3_DLOS_POS 1 #define D40_MEM_LCSP3_DTCP_POS 0 +#define D40_MEM_LCSP3_DPTR_MASK (0xFFFF << D40_MEM_LCSP3_DPTR_POS) #define D40_MEM_LCSP3_DLOS_MASK (0x7F << D40_MEM_LCSP3_DLOS_POS) #define D40_MEM_LCSP3_DTCP_MASK (0x1 << D40_MEM_LCSP3_DTCP_POS) @@ -123,9 +129,21 @@ /* DMA Register Offsets */ #define D40_DREG_GCC 0x000 +#define D40_DREG_GCC_ENA 0x1 +/* This assumes that there are only 4 event groups */ +#define D40_DREG_GCC_ENABLE_ALL 0xff01 +#define D40_DREG_GCC_EVTGRP_POS 8 +#define D40_DREG_GCC_SRC 0 +#define D40_DREG_GCC_DST 1 +#define D40_DREG_GCC_EVTGRP_ENA(x, y) \ + (1 << (D40_DREG_GCC_EVTGRP_POS + 2 * x + y)) + #define D40_DREG_PRTYP 0x004 #define D40_DREG_PRSME 0x008 #define D40_DREG_PRSMO 0x00C +#define D40_DREG_PRSM_MODE_MASK 0x3 +#define D40_DREG_PRSM_MODE_SECURE 0x1 + #define D40_DREG_PRMSE 0x010 #define D40_DREG_PRMSO 0x014 #define D40_DREG_PRMOE 0x018 @@ -290,13 +308,6 @@ struct d40_def_lcsp { /* Physical channels */ -enum d40_lli_flags { - LLI_ADDR_INC = 1 << 0, - LLI_TERM_INT = 1 << 1, - LLI_CYCLIC = 1 << 2, - LLI_LAST_LINK = 1 << 3, -}; - void d40_phy_cfg(struct stedma40_chan_cfg *cfg, u32 *src_cfg, u32 *dst_cfg, @@ -312,27 +323,58 @@ int d40_phy_sg_to_lli(struct scatterlist *sg, struct d40_phy_lli *lli, dma_addr_t lli_phys, u32 reg_cfg, - struct stedma40_half_channel_info *info, - struct stedma40_half_channel_info *otherinfo, - unsigned long flags); + u32 data_width, + int psize, + bool cyclic, + bool cyclic_int); + +int d40_phy_fill_lli(struct d40_phy_lli *lli, + dma_addr_t data, + u32 data_size, + int psize, + dma_addr_t next_lli, + u32 reg_cfg, + bool term_int, + u32 data_width, + bool is_device); + +void d40_phy_lli_write(void __iomem *virtbase, + u32 phy_chan_num, + struct d40_phy_lli *lli_dst, + struct d40_phy_lli *lli_src); /* Logical channels */ +void d40_log_fill_lli(struct d40_log_lli *lli, + dma_addr_t data, + u32 data_size, + u32 reg_cfg, + u32 data_width, + bool addr_inc); + +int d40_log_sg_to_dev(struct scatterlist *sg, + int sg_len, + struct d40_log_lli_bidir *lli, + struct d40_def_lcsp *lcsp, + u32 src_data_width, + u32 dst_data_width, + enum dma_data_direction direction, + dma_addr_t dev_addr); + int d40_log_sg_to_lli(struct scatterlist *sg, int sg_len, - dma_addr_t dev_addr, struct d40_log_lli *lli_sg, u32 lcsp13, /* src or dst*/ - u32 data_width1, u32 data_width2); + u32 data_width); void d40_log_lli_lcpa_write(struct d40_log_lli_full *lcpa, struct d40_log_lli *lli_dst, struct d40_log_lli *lli_src, - int next, unsigned int flags); + int next, bool interrupt); void d40_log_lli_lcla_write(struct d40_log_lli *lcla, struct d40_log_lli *lli_dst, struct d40_log_lli *lli_src, - int next, unsigned int flags); + int next, bool interrupt); #endif /* STE_DMA40_LLI_H */ diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index d539efd96d4..92baa415b6d 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -477,7 +477,7 @@ config GPIO_JANZ_TTL config GPIO_AB8500 bool "ST-Ericsson AB8500 Mixed Signal Circuit gpio functions" - depends on AB8500_CORE && BROKEN + depends on AB8500_CORE help Select this to enable the AB8500 IC GPIO driver diff --git a/drivers/gpio/gpio-ab8500.c b/drivers/gpio/gpio-ab8500.c index 050c05d9189..a0253ad14df 100644 --- a/drivers/gpio/gpio-ab8500.c +++ b/drivers/gpio/gpio-ab8500.c @@ -60,7 +60,7 @@ #define AB8500_GPIO_IN4_REG 0x43 #define AB8500_GPIO_IN5_REG 0x44 #define AB8500_GPIO_IN6_REG 0x45 -#define AB8500_GPIO_ALTFUN_REG 0x45 +#define AB8500_GPIO_ALTFUN_REG 0x50 #define ALTFUN_REG_INDEX 6 #define AB8500_NUM_GPIO 42 #define AB8500_NUM_VIR_GPIO_IRQ 16 @@ -115,7 +115,7 @@ static int ab8500_gpio_get(struct gpio_chip *chip, unsigned offset) { struct ab8500_gpio *ab8500_gpio = to_ab8500_gpio(chip); u8 mask = 1 << (offset % 8); - u8 reg = AB8500_GPIO_OUT1_REG + (offset / 8); + u8 reg = AB8500_GPIO_IN1_REG + (offset / 8); int ret; u8 data; ret = abx500_get_register_interruptible(ab8500_gpio->dev, AB8500_MISC, @@ -132,7 +132,7 @@ static void ab8500_gpio_set(struct gpio_chip *chip, unsigned offset, int val) struct ab8500_gpio *ab8500_gpio = to_ab8500_gpio(chip); int ret; /* Write the data */ - ret = ab8500_gpio_set_bits(chip, AB8500_GPIO_OUT1_REG, offset, 1); + ret = ab8500_gpio_set_bits(chip, AB8500_GPIO_OUT1_REG, offset, val); if (ret < 0) dev_err(ab8500_gpio->dev, "%s write failed\n", __func__); } @@ -174,9 +174,9 @@ static int ab8500_gpio_to_irq(struct gpio_chip *chip, unsigned offset) int start; int end; } clusters[] = { - {.start = 6, .end = 13}, - {.start = 24, .end = 25}, - {.start = 36, .end = 41}, + {.start = 5, .end = 12}, /* GPIO numbers start from 1 */ + {.start = 23, .end = 24}, + {.start = 35, .end = 40}, }; struct ab8500_gpio *ab8500_gpio = to_ab8500_gpio(chip); int base = ab8500_gpio->irq_base; @@ -207,7 +207,7 @@ static struct gpio_chip ab8500gpio_chip = { static unsigned int irq_to_rising(unsigned int irq) { - struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + struct ab8500_gpio *ab8500_gpio = irq_get_chip_data(irq); int offset = irq - ab8500_gpio->irq_base; int new_irq = offset + AB8500_INT_GPIO6R + ab8500_gpio->parent->irq_base; @@ -216,7 +216,7 @@ static unsigned int irq_to_rising(unsigned int irq) static unsigned int irq_to_falling(unsigned int irq) { - struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + struct ab8500_gpio *ab8500_gpio = irq_get_chip_data(irq); int offset = irq - ab8500_gpio->irq_base; int new_irq = offset + AB8500_INT_GPIO6F + ab8500_gpio->parent->irq_base; @@ -261,15 +261,16 @@ static irqreturn_t handle_falling(int irq, void *dev) return IRQ_HANDLED; } -static void ab8500_gpio_irq_lock(unsigned int irq) +static void ab8500_gpio_irq_lock(struct irq_data *data) { - struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + struct ab8500_gpio *ab8500_gpio = irq_data_get_irq_chip_data(data); mutex_lock(&ab8500_gpio->lock); } -static void ab8500_gpio_irq_sync_unlock(unsigned int irq) +static void ab8500_gpio_irq_sync_unlock(struct irq_data *data) { - struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + struct ab8500_gpio *ab8500_gpio = irq_data_get_irq_chip_data(data); + unsigned int irq = data->irq; int offset = irq - ab8500_gpio->irq_base; bool rising = ab8500_gpio->rising & BIT(offset); bool falling = ab8500_gpio->falling & BIT(offset); @@ -316,21 +317,22 @@ static void ab8500_gpio_irq_sync_unlock(unsigned int irq) } -static void ab8500_gpio_irq_mask(unsigned int irq) +static void ab8500_gpio_irq_mask(struct irq_data *data) { - struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + struct ab8500_gpio *ab8500_gpio = irq_data_get_irq_chip_data(data); ab8500_gpio->irq_action = MASK; } -static void ab8500_gpio_irq_unmask(unsigned int irq) +static void ab8500_gpio_irq_unmask(struct irq_data *data) { - struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + struct ab8500_gpio *ab8500_gpio = irq_data_get_irq_chip_data(data); ab8500_gpio->irq_action = UNMASK; } -static int ab8500_gpio_irq_set_type(unsigned int irq, unsigned int type) +static int ab8500_gpio_irq_set_type(struct irq_data *data, unsigned int type) { - struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + struct ab8500_gpio *ab8500_gpio = irq_data_get_irq_chip_data(data); + unsigned int irq = data->irq; int offset = irq - ab8500_gpio->irq_base; if (type == IRQ_TYPE_EDGE_BOTH) { @@ -344,28 +346,28 @@ static int ab8500_gpio_irq_set_type(unsigned int irq, unsigned int type) return 0; } -unsigned int ab8500_gpio_irq_startup(unsigned int irq) +unsigned int ab8500_gpio_irq_startup(struct irq_data *data) { - struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + struct ab8500_gpio *ab8500_gpio = irq_data_get_irq_chip_data(data); ab8500_gpio->irq_action = STARTUP; return 0; } -void ab8500_gpio_irq_shutdown(unsigned int irq) +void ab8500_gpio_irq_shutdown(struct irq_data *data) { - struct ab8500_gpio *ab8500_gpio = get_irq_chip_data(irq); + struct ab8500_gpio *ab8500_gpio = irq_data_get_irq_chip_data(data); ab8500_gpio->irq_action = SHUTDOWN; } static struct irq_chip ab8500_gpio_irq_chip = { .name = "ab8500-gpio", - .startup = ab8500_gpio_irq_startup, - .shutdown = ab8500_gpio_irq_shutdown, - .bus_lock = ab8500_gpio_irq_lock, - .bus_sync_unlock = ab8500_gpio_irq_sync_unlock, - .mask = ab8500_gpio_irq_mask, - .unmask = ab8500_gpio_irq_unmask, - .set_type = ab8500_gpio_irq_set_type, + .irq_startup = ab8500_gpio_irq_startup, + .irq_shutdown = ab8500_gpio_irq_shutdown, + .irq_bus_lock = ab8500_gpio_irq_lock, + .irq_bus_sync_unlock = ab8500_gpio_irq_sync_unlock, + .irq_mask = ab8500_gpio_irq_mask, + .irq_unmask = ab8500_gpio_irq_unmask, + .irq_set_type = ab8500_gpio_irq_set_type, }; static int ab8500_gpio_irq_init(struct ab8500_gpio *ab8500_gpio) @@ -374,14 +376,14 @@ static int ab8500_gpio_irq_init(struct ab8500_gpio *ab8500_gpio) int irq; for (irq = base; irq < base + AB8500_NUM_VIR_GPIO_IRQ ; irq++) { - set_irq_chip_data(irq, ab8500_gpio); - set_irq_chip_and_handler(irq, &ab8500_gpio_irq_chip, + irq_set_chip_data(irq, ab8500_gpio); + irq_set_chip_and_handler(irq, &ab8500_gpio_irq_chip, handle_simple_irq); - set_irq_nested_thread(irq, 1); + irq_set_nested_thread(irq, 1); #ifdef CONFIG_ARM set_irq_flags(irq, IRQF_VALID); #else - set_irq_noprobe(irq); + irq_set_noprobe(irq); #endif } @@ -397,8 +399,8 @@ static void ab8500_gpio_irq_remove(struct ab8500_gpio *ab8500_gpio) #ifdef CONFIG_ARM set_irq_flags(irq, 0); #endif - set_irq_chip_and_handler(irq, NULL, NULL); - set_irq_chip_data(irq, NULL); + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); } } @@ -443,6 +445,18 @@ static int __devinit ab8500_gpio_probe(struct platform_device *pdev) pdata->config_reg[i]); if (ret < 0) goto out_free; + + ret = abx500_set_register_interruptible(ab8500_gpio->dev, + AB8500_MISC, i + AB8500_GPIO_DIR1_REG, + pdata->config_direction[i]); + if (ret < 0) + goto out_free; + + ret = abx500_set_register_interruptible(ab8500_gpio->dev, + AB8500_MISC, i + AB8500_GPIO_PUD1_REG, + pdata->config_pullups[i]); + if (ret < 0) + goto out_free; } ret = abx500_set_register_interruptible(ab8500_gpio->dev, AB8500_MISC, AB8500_GPIO_ALTFUN_REG, @@ -493,6 +507,86 @@ static int __devexit ab8500_gpio_remove(struct platform_device *pdev) return 0; } +int ab8500_config_pulldown(struct device *dev, + enum ab8500_pin gpio, bool enable) +{ + u8 offset = gpio - AB8500_PIN_GPIO1; + u8 pos = offset % 8; + u8 val = enable ? 0 : 1; + u8 reg = AB8500_GPIO_PUD1_REG + (offset / 8); + int ret; + + ret = abx500_mask_and_set_register_interruptible(dev, + AB8500_MISC, reg, 1 << pos, val << pos); + if (ret < 0) + dev_err(dev, "%s write failed\n", __func__); + return ret; +} +EXPORT_SYMBOL(ab8500_config_pulldown); + +/* + * ab8500_gpio_config_select() + * + * Configure functionality of pin, either specific use or GPIO. + * @dev: device pointer + * @gpio: gpio number + * @gpio_select: true if the pin should be used as GPIO + */ +int ab8500_gpio_config_select(struct device *dev, + enum ab8500_pin gpio, bool gpio_select) +{ + u8 offset = gpio - AB8500_PIN_GPIO1; + u8 reg = AB8500_GPIO_SEL1_REG + (offset / 8); + u8 pos = offset % 8; + u8 val = gpio_select ? 1 : 0; + int ret; + + ret = abx500_mask_and_set_register_interruptible(dev, + AB8500_MISC, reg, 1 << pos, val << pos); + if (ret < 0) + dev_err(dev, "%s write failed\n", __func__); + + dev_vdbg(dev, "%s (bank, addr, mask, value): 0x%x, 0x%x, 0x%x, 0x%x\n", + __func__, AB8500_MISC, reg, 1 << pos, val << pos); + + return ret; +} + +/* + * ab8500_gpio_config_get_select() + * + * Read currently configured functionality, either specific use or GPIO. + * @dev: device pointer + * @gpio: gpio number + * @gpio_select: pointer to pin selection status + */ +int ab8500_gpio_config_get_select(struct device *dev, + enum ab8500_pin gpio, bool *gpio_select) +{ + u8 offset = gpio - AB8500_PIN_GPIO1; + u8 reg = AB8500_GPIO_SEL1_REG + (offset / 8); + u8 pos = offset % 8; + u8 val; + int ret; + + ret = abx500_get_register_interruptible(dev, + AB8500_MISC, reg, &val); + if (ret < 0) { + dev_err(dev, "%s read failed\n", __func__); + return ret; + } + + if (val & (1 << pos)) + *gpio_select = true; + else + *gpio_select = false; + + dev_vdbg(dev, "%s (bank, addr, mask, value): 0x%x, 0x%x, 0x%x, 0x%x\n", + __func__, AB8500_MISC, reg, 1 << pos, val); + + return 0; +} + static struct platform_driver ab8500_gpio_driver = { .driver = { .name = "ab8500-gpio", diff --git a/drivers/gpio/gpio-nomadik.c b/drivers/gpio/gpio-nomadik.c index 2c212c732d7..b36b6ead9d0 100644 --- a/drivers/gpio/gpio-nomadik.c +++ b/drivers/gpio/gpio-nomadik.c @@ -23,6 +23,7 @@ #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/slab.h> +#include <linux/gpio/nomadik.h> #include <asm/mach/irq.h> @@ -57,9 +58,10 @@ struct nmk_gpio_chip { u32 real_wake; u32 rwimsc; u32 fwimsc; - u32 slpm; - u32 enabled; + u32 rimsc; + u32 fimsc; u32 pull_up; + u32 lowemi; }; static struct nmk_gpio_chip * @@ -124,6 +126,24 @@ static void __nmk_gpio_set_pull(struct nmk_gpio_chip *nmk_chip, } } +static void __nmk_gpio_set_lowemi(struct nmk_gpio_chip *nmk_chip, + unsigned offset, bool lowemi) +{ + u32 bit = BIT(offset); + bool enabled = nmk_chip->lowemi & bit; + + if (lowemi == enabled) + return; + + if (lowemi) + nmk_chip->lowemi |= bit; + else + nmk_chip->lowemi &= ~bit; + + writel_relaxed(nmk_chip->lowemi, + nmk_chip->addr + NMK_GPIO_LOWEMI); +} + static void __nmk_gpio_make_input(struct nmk_gpio_chip *nmk_chip, unsigned offset) { @@ -150,8 +170,8 @@ static void __nmk_gpio_set_mode_safe(struct nmk_gpio_chip *nmk_chip, unsigned offset, int gpio_mode, bool glitch) { - u32 rwimsc = readl(nmk_chip->addr + NMK_GPIO_RWIMSC); - u32 fwimsc = readl(nmk_chip->addr + NMK_GPIO_FWIMSC); + u32 rwimsc = nmk_chip->rwimsc; + u32 fwimsc = nmk_chip->fwimsc; if (glitch && nmk_chip->set_ioforce) { u32 bit = BIT(offset); @@ -173,6 +193,36 @@ static void __nmk_gpio_set_mode_safe(struct nmk_gpio_chip *nmk_chip, } } +static void +nmk_gpio_disable_lazy_irq(struct nmk_gpio_chip *nmk_chip, unsigned offset) +{ + u32 falling = nmk_chip->fimsc & BIT(offset); + u32 rising = nmk_chip->rimsc & BIT(offset); + int gpio = nmk_chip->chip.base + offset; + int irq = NOMADIK_GPIO_TO_IRQ(gpio); + struct irq_data *d = irq_get_irq_data(irq); + + if (!rising && !falling) + return; + + if (!d || !irqd_irq_disabled(d)) + return; + + if (rising) { + nmk_chip->rimsc &= ~BIT(offset); + writel_relaxed(nmk_chip->rimsc, + nmk_chip->addr + NMK_GPIO_RIMSC); + } + + if (falling) { + nmk_chip->fimsc &= ~BIT(offset); + writel_relaxed(nmk_chip->fimsc, + nmk_chip->addr + NMK_GPIO_FIMSC); + } + + dev_dbg(nmk_chip->chip.dev, "%d: clearing interrupt mask\n", gpio); +} + static void __nmk_config_pin(struct nmk_gpio_chip *nmk_chip, unsigned offset, pin_cfg_t cfg, bool sleep, unsigned int *slpmregs) { @@ -238,6 +288,17 @@ static void __nmk_config_pin(struct nmk_gpio_chip *nmk_chip, unsigned offset, __nmk_gpio_set_pull(nmk_chip, offset, pull); } + __nmk_gpio_set_lowemi(nmk_chip, offset, PIN_LOWEMI(cfg)); + + /* + * If the pin is switching to altfunc, and there was an interrupt + * installed on it which has been lazy disabled, actually mask the + * interrupt to prevent spurious interrupts that would occur while the + * pin is under control of the peripheral. Only SKE does this. + */ + if (af != NMK_GPIO_ALT_GPIO) + nmk_gpio_disable_lazy_irq(nmk_chip, offset); + /* * If we've backed up the SLPM registers (glitch workaround), modify * the backups since they will be restored. @@ -276,6 +337,8 @@ static void nmk_gpio_glitch_slpm_init(unsigned int *slpm) if (!chip) break; + clk_enable(chip->clk); + slpm[i] = readl(chip->addr + NMK_GPIO_SLPC); writel(temp, chip->addr + NMK_GPIO_SLPC); } @@ -292,6 +355,8 @@ static void nmk_gpio_glitch_slpm_restore(unsigned int *slpm) break; writel(slpm[i], chip->addr + NMK_GPIO_SLPC); + + clk_disable(chip->clk); } } @@ -336,10 +401,12 @@ static int __nmk_config_pins(pin_cfg_t *cfgs, int num, bool sleep) break; } + clk_enable(nmk_chip->clk); spin_lock(&nmk_chip->lock); __nmk_config_pin(nmk_chip, pin - nmk_chip->chip.base, cfgs[i], sleep, glitch ? slpm : NULL); spin_unlock(&nmk_chip->lock); + clk_disable(nmk_chip->clk); } if (glitch) @@ -353,7 +420,7 @@ static int __nmk_config_pins(pin_cfg_t *cfgs, int num, bool sleep) /** * nmk_config_pin - configure a pin's mux attributes * @cfg: pin confguration - * + * @sleep: Non-zero to apply the sleep mode configuration * Configures a pin's mode (alternate function or GPIO), its pull up status, * and its sleep mode based on the specified configuration. The @cfg is * usually one of the SoC specific macros defined in mach/<soc>-pins.h. These @@ -424,6 +491,7 @@ int nmk_gpio_set_slpm(int gpio, enum nmk_gpio_slpm mode) if (!nmk_chip) return -EINVAL; + clk_enable(nmk_chip->clk); spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); spin_lock(&nmk_chip->lock); @@ -431,6 +499,7 @@ int nmk_gpio_set_slpm(int gpio, enum nmk_gpio_slpm mode) spin_unlock(&nmk_chip->lock); spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); + clk_disable(nmk_chip->clk); return 0; } @@ -457,9 +526,11 @@ int nmk_gpio_set_pull(int gpio, enum nmk_gpio_pull pull) if (!nmk_chip) return -EINVAL; + clk_enable(nmk_chip->clk); spin_lock_irqsave(&nmk_chip->lock, flags); __nmk_gpio_set_pull(nmk_chip, gpio - nmk_chip->chip.base, pull); spin_unlock_irqrestore(&nmk_chip->lock, flags); + clk_disable(nmk_chip->clk); return 0; } @@ -483,9 +554,11 @@ int nmk_gpio_set_mode(int gpio, int gpio_mode) if (!nmk_chip) return -EINVAL; + clk_enable(nmk_chip->clk); spin_lock_irqsave(&nmk_chip->lock, flags); __nmk_gpio_set_mode(nmk_chip, gpio - nmk_chip->chip.base, gpio_mode); spin_unlock_irqrestore(&nmk_chip->lock, flags); + clk_disable(nmk_chip->clk); return 0; } @@ -502,9 +575,13 @@ int nmk_gpio_get_mode(int gpio) bit = 1 << (gpio - nmk_chip->chip.base); + clk_enable(nmk_chip->clk); + afunc = readl(nmk_chip->addr + NMK_GPIO_AFSLA) & bit; bfunc = readl(nmk_chip->addr + NMK_GPIO_AFSLB) & bit; + clk_disable(nmk_chip->clk); + return (afunc ? NMK_GPIO_ALT_A : 0) | (bfunc ? NMK_GPIO_ALT_B : 0); } EXPORT_SYMBOL(nmk_gpio_get_mode); @@ -525,7 +602,10 @@ static void nmk_gpio_irq_ack(struct irq_data *d) nmk_chip = irq_data_get_irq_chip_data(d); if (!nmk_chip) return; + + clk_enable(nmk_chip->clk); writel(nmk_gpio_get_bitmask(gpio), nmk_chip->addr + NMK_GPIO_IC); + clk_disable(nmk_chip->clk); } enum nmk_gpio_irq_type { @@ -537,37 +617,52 @@ static void __nmk_gpio_irq_modify(struct nmk_gpio_chip *nmk_chip, int gpio, enum nmk_gpio_irq_type which, bool enable) { - u32 rimsc = which == WAKE ? NMK_GPIO_RWIMSC : NMK_GPIO_RIMSC; - u32 fimsc = which == WAKE ? NMK_GPIO_FWIMSC : NMK_GPIO_FIMSC; u32 bitmask = nmk_gpio_get_bitmask(gpio); - u32 reg; + u32 *rimscval; + u32 *fimscval; + u32 rimscreg; + u32 fimscreg; + + if (which == NORMAL) { + rimscreg = NMK_GPIO_RIMSC; + fimscreg = NMK_GPIO_FIMSC; + rimscval = &nmk_chip->rimsc; + fimscval = &nmk_chip->fimsc; + } else { + rimscreg = NMK_GPIO_RWIMSC; + fimscreg = NMK_GPIO_FWIMSC; + rimscval = &nmk_chip->rwimsc; + fimscval = &nmk_chip->fwimsc; + } /* we must individually set/clear the two edges */ if (nmk_chip->edge_rising & bitmask) { - reg = readl(nmk_chip->addr + rimsc); if (enable) - reg |= bitmask; + *rimscval |= bitmask; else - reg &= ~bitmask; - writel(reg, nmk_chip->addr + rimsc); + *rimscval &= ~bitmask; + writel(*rimscval, nmk_chip->addr + rimscreg); } if (nmk_chip->edge_falling & bitmask) { - reg = readl(nmk_chip->addr + fimsc); if (enable) - reg |= bitmask; + *fimscval |= bitmask; else - reg &= ~bitmask; - writel(reg, nmk_chip->addr + fimsc); + *fimscval &= ~bitmask; + writel(*fimscval, nmk_chip->addr + fimscreg); } } static void __nmk_gpio_set_wake(struct nmk_gpio_chip *nmk_chip, int gpio, bool on) { - if (nmk_chip->sleepmode) { + /* + * Ensure WAKEUP_ENABLE is on. No need to disable it if wakeup is + * disabled, since setting SLPM to 1 increases power consumption, and + * wakeup is anyhow controlled by the RIMSC and FIMSC registers. + */ + if (nmk_chip->sleepmode && on) { __nmk_gpio_set_slpm(nmk_chip, gpio - nmk_chip->chip.base, - on ? NMK_GPIO_SLPM_WAKEUP_ENABLE - : NMK_GPIO_SLPM_WAKEUP_DISABLE); + NMK_GPIO_SLPM_WAKEUP_ENABLE); } __nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, on); @@ -586,11 +681,7 @@ static int nmk_gpio_irq_maskunmask(struct irq_data *d, bool enable) if (!nmk_chip) return -EINVAL; - if (enable) - nmk_chip->enabled |= bitmask; - else - nmk_chip->enabled &= ~bitmask; - + clk_enable(nmk_chip->clk); spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); spin_lock(&nmk_chip->lock); @@ -601,6 +692,7 @@ static int nmk_gpio_irq_maskunmask(struct irq_data *d, bool enable) spin_unlock(&nmk_chip->lock); spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); + clk_disable(nmk_chip->clk); return 0; } @@ -628,10 +720,11 @@ static int nmk_gpio_irq_set_wake(struct irq_data *d, unsigned int on) return -EINVAL; bitmask = nmk_gpio_get_bitmask(gpio); + clk_enable(nmk_chip->clk); spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); spin_lock(&nmk_chip->lock); - if (!(nmk_chip->enabled & bitmask)) + if (irqd_irq_disabled(d)) __nmk_gpio_set_wake(nmk_chip, gpio, on); if (on) @@ -641,13 +734,15 @@ static int nmk_gpio_irq_set_wake(struct irq_data *d, unsigned int on) spin_unlock(&nmk_chip->lock); spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); + clk_disable(nmk_chip->clk); return 0; } static int nmk_gpio_irq_set_type(struct irq_data *d, unsigned int type) { - bool enabled, wake = irqd_is_wakeup_set(d); + bool enabled = !irqd_irq_disabled(d); + bool wake = irqd_is_wakeup_set(d); int gpio; struct nmk_gpio_chip *nmk_chip; unsigned long flags; @@ -664,8 +759,7 @@ static int nmk_gpio_irq_set_type(struct irq_data *d, unsigned int type) if (type & IRQ_TYPE_LEVEL_LOW) return -EINVAL; - enabled = nmk_chip->enabled & bitmask; - + clk_enable(nmk_chip->clk); spin_lock_irqsave(&nmk_chip->lock, flags); if (enabled) @@ -689,10 +783,28 @@ static int nmk_gpio_irq_set_type(struct irq_data *d, unsigned int type) __nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, true); spin_unlock_irqrestore(&nmk_chip->lock, flags); + clk_disable(nmk_chip->clk); + + return 0; +} +static unsigned int nmk_gpio_irq_startup(struct irq_data *d) +{ + struct nmk_gpio_chip *nmk_chip = irq_data_get_irq_chip_data(d); + + clk_enable(nmk_chip->clk); + nmk_gpio_irq_unmask(d); return 0; } +static void nmk_gpio_irq_shutdown(struct irq_data *d) +{ + struct nmk_gpio_chip *nmk_chip = irq_data_get_irq_chip_data(d); + + nmk_gpio_irq_mask(d); + clk_disable(nmk_chip->clk); +} + static struct irq_chip nmk_gpio_irq_chip = { .name = "Nomadik-GPIO", .irq_ack = nmk_gpio_irq_ack, @@ -700,6 +812,8 @@ static struct irq_chip nmk_gpio_irq_chip = { .irq_unmask = nmk_gpio_irq_unmask, .irq_set_type = nmk_gpio_irq_set_type, .irq_set_wake = nmk_gpio_irq_set_wake, + .irq_startup = nmk_gpio_irq_startup, + .irq_shutdown = nmk_gpio_irq_shutdown, }; static void __nmk_gpio_irq_handler(unsigned int irq, struct irq_desc *desc, @@ -726,7 +840,11 @@ static void __nmk_gpio_irq_handler(unsigned int irq, struct irq_desc *desc, static void nmk_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) { struct nmk_gpio_chip *nmk_chip = irq_get_handler_data(irq); - u32 status = readl(nmk_chip->addr + NMK_GPIO_IS); + u32 status; + + clk_enable(nmk_chip->clk); + status = readl(nmk_chip->addr + NMK_GPIO_IS); + clk_disable(nmk_chip->clk); __nmk_gpio_irq_handler(irq, desc, status); } @@ -772,7 +890,12 @@ static int nmk_gpio_make_input(struct gpio_chip *chip, unsigned offset) struct nmk_gpio_chip *nmk_chip = container_of(chip, struct nmk_gpio_chip, chip); + clk_enable(nmk_chip->clk); + writel(1 << offset, nmk_chip->addr + NMK_GPIO_DIRC); + + clk_disable(nmk_chip->clk); + return 0; } @@ -781,8 +904,15 @@ static int nmk_gpio_get_input(struct gpio_chip *chip, unsigned offset) struct nmk_gpio_chip *nmk_chip = container_of(chip, struct nmk_gpio_chip, chip); u32 bit = 1 << offset; + int value; + + clk_enable(nmk_chip->clk); + + value = (readl(nmk_chip->addr + NMK_GPIO_DAT) & bit) != 0; - return (readl(nmk_chip->addr + NMK_GPIO_DAT) & bit) != 0; + clk_disable(nmk_chip->clk); + + return value; } static void nmk_gpio_set_output(struct gpio_chip *chip, unsigned offset, @@ -791,7 +921,11 @@ static void nmk_gpio_set_output(struct gpio_chip *chip, unsigned offset, struct nmk_gpio_chip *nmk_chip = container_of(chip, struct nmk_gpio_chip, chip); + clk_enable(nmk_chip->clk); + __nmk_gpio_set_output(nmk_chip, offset, val); + + clk_disable(nmk_chip->clk); } static int nmk_gpio_make_output(struct gpio_chip *chip, unsigned offset, @@ -800,8 +934,12 @@ static int nmk_gpio_make_output(struct gpio_chip *chip, unsigned offset, struct nmk_gpio_chip *nmk_chip = container_of(chip, struct nmk_gpio_chip, chip); + clk_enable(nmk_chip->clk); + __nmk_gpio_make_output(nmk_chip, offset, val); + clk_disable(nmk_chip->clk); + return 0; } @@ -832,6 +970,8 @@ static void nmk_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) [NMK_GPIO_ALT_C] = "altC", }; + clk_enable(nmk_chip->clk); + for (i = 0; i < chip->ngpio; i++, gpio++) { const char *label = gpiochip_is_requested(chip, i); bool pull; @@ -876,6 +1016,8 @@ static void nmk_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) seq_printf(s, "\n"); } + + clk_disable(nmk_chip->clk); } #else @@ -893,6 +1035,34 @@ static struct gpio_chip nmk_gpio_template = { .can_sleep = 0, }; +void nmk_gpio_clocks_enable(void) +{ + int i; + + for (i = 0; i < NUM_BANKS; i++) { + struct nmk_gpio_chip *chip = nmk_gpio_chips[i]; + + if (!chip) + continue; + + clk_enable(chip->clk); + } +} + +void nmk_gpio_clocks_disable(void) +{ + int i; + + for (i = 0; i < NUM_BANKS; i++) { + struct nmk_gpio_chip *chip = nmk_gpio_chips[i]; + + if (!chip) + continue; + + clk_disable(chip->clk); + } +} + /* * Called from the suspend/resume path to only keep the real wakeup interrupts * (those that have had set_irq_wake() called on them) as wakeup interrupts, @@ -912,20 +1082,14 @@ void nmk_gpio_wakeups_suspend(void) if (!chip) break; - chip->rwimsc = readl(chip->addr + NMK_GPIO_RWIMSC); - chip->fwimsc = readl(chip->addr + NMK_GPIO_FWIMSC); + clk_enable(chip->clk); writel(chip->rwimsc & chip->real_wake, chip->addr + NMK_GPIO_RWIMSC); writel(chip->fwimsc & chip->real_wake, chip->addr + NMK_GPIO_FWIMSC); - if (chip->sleepmode) { - chip->slpm = readl(chip->addr + NMK_GPIO_SLPC); - - /* 0 -> wakeup enable */ - writel(~chip->real_wake, chip->addr + NMK_GPIO_SLPC); - } + clk_disable(chip->clk); } } @@ -939,11 +1103,12 @@ void nmk_gpio_wakeups_resume(void) if (!chip) break; + clk_enable(chip->clk); + writel(chip->rwimsc, chip->addr + NMK_GPIO_RWIMSC); writel(chip->fwimsc, chip->addr + NMK_GPIO_FWIMSC); - if (chip->sleepmode) - writel(chip->slpm, chip->addr + NMK_GPIO_SLPC); + clk_disable(chip->clk); } } @@ -1010,8 +1175,6 @@ static int __devinit nmk_gpio_probe(struct platform_device *dev) goto out_release; } - clk_enable(clk); - nmk_chip = kzalloc(sizeof(*nmk_chip), GFP_KERNEL); if (!nmk_chip) { ret = -ENOMEM; @@ -1039,6 +1202,10 @@ static int __devinit nmk_gpio_probe(struct platform_device *dev) chip->dev = &dev->dev; chip->owner = THIS_MODULE; + clk_enable(nmk_chip->clk); + nmk_chip->lowemi = readl_relaxed(nmk_chip->addr + NMK_GPIO_LOWEMI); + clk_disable(nmk_chip->clk); + ret = gpiochip_add(&nmk_chip->chip); if (ret) goto out_free; diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 0b62c3c6b7c..b0e5152f7f5 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -39,6 +39,44 @@ config HWMON_DEBUG_CHIP comment "Native drivers" +config SENSORS_AB8500 + tristate "AB8500 thermal monitoring" + depends on AB8500_GPADC + default n + help + If you say yes here you get support for the thermal sensor part + of the AB8500 chip. The driver includes thermal management for + AB8500 die and two GPADC channels. The GPADC channel are preferably + used to access sensors outside the AB8500 chip. + + This driver can also be built as a module. If so, the module + will be called abx500-temp. + +config SENSORS_AB5500 + tristate "AB5500 thermal monitoring" + depends on AB5500_GPADC + default n + help + If you say yes here you get support for the thermal sensor part + of the AB5500 chip. The driver includes thermal management for + AB5500 die, pcb and RF XTAL temperature. + + This driver can also be built as a module. If so, the module + will be called abx500-temp. + +config SENSORS_DBX500 + tristate "DBX500 thermal monitoring" + depends on MFD_DB8500_PRCMU || MFD_DB5500_PRCMU + default n + help + If you say yes here you get support for the thermal sensor part + of the DBX500 chip. The driver includes thermal management for + DBX500 die. + + This driver can also be built as a module. If so, the module + will be called dbx500_temp. + + config SENSORS_ABITUGURU tristate "Abit uGuru (rev 1 & 2)" depends on X86 && DMI && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 3c9ccefea79..974e23cf262 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -19,6 +19,9 @@ obj-$(CONFIG_SENSORS_W83795) += w83795.o obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o +obj-$(CONFIG_SENSORS_AB8500) += abx500.o ab8500.o +obj-$(CONFIG_SENSORS_AB5500) += abx500.o ab5500.o +obj-$(CONFIG_SENSORS_DBX500) += dbx500.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o obj-$(CONFIG_SENSORS_AD7414) += ad7414.o diff --git a/drivers/hwmon/ab5500.c b/drivers/hwmon/ab5500.c new file mode 100644 index 00000000000..0122f315ac6 --- /dev/null +++ b/drivers/hwmon/ab5500.c @@ -0,0 +1,207 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Martin Persson <martin.persson@stericsson.com> for + * ST-Ericsson. + * License terms: GNU Gereral Public License (GPL) version 2 + * + * Note: + * + * If/when the AB5500 thermal warning temperature is reached (threshold + * 125C cannot be changed by SW), an interrupt is set and the driver + * notifies user space via a sysfs event. If a shut down is not + * triggered by user space and temperature reaches beyond critical + * limit(130C) pm_power off is called. + * + * If/when AB5500 thermal shutdown temperature is reached a hardware + * shutdown of the AB5500 will occur. + */ + +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/sysfs.h> +#include <linux/hwmon-sysfs.h> +#include <linux/platform_device.h> +#include <linux/mfd/abx500/ab5500-gpadc.h> +#include <linux/mfd/abx500/ab5500-bm.h> +#include "abx500.h" +#include <asm/mach-types.h> + +/* AB5500 driver monitors GPADC - XTAL_TEMP, PCB_TEMP, + * BTEMP_BALL, BAT_CTRL and DIE_TEMP + */ +#define NUM_MONITORED_SENSORS 5 + +#define SHUTDOWN_AUTO_MIN_LIMIT -25 +#define SHUTDOWN_AUTO_MAX_LIMIT 130 + +static int ab5500_output_convert(int val, u8 sensor) +{ + int res = val; + /* GPADC returns die temperature in Celsius + * convert it to millidegree celsius + */ + if (sensor == DIE_TEMP) + res = val * 1000; + + return res; +} + +static int ab5500_read_sensor(struct abx500_temp *data, u8 sensor) +{ + int val; + /* + * Special treatment for BAT_CTRL node, since this + * temperature measurement is more complex than just + * an ADC readout + */ + if (sensor == BAT_CTRL) + val = ab5500_btemp_get_batctrl_temp(data->ab5500_btemp); + else + val = ab5500_gpadc_convert(data->ab5500_gpadc, sensor); + + if (val < 0) + return val; + else + return ab5500_output_convert(val, sensor); +} + +static ssize_t ab5500_show_name(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "ab5500\n"); +} + +static ssize_t ab5500_show_label(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + char *name; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int index = attr->index; + + /* + * Make sure these labels correspond to the attribute indexes + * used when calling SENSOR_DEVICE_ATRR. + * Temperature sensors outside ab8500 (read via GPADC) are marked + * with prefix ext_ + */ + switch (index) { + case 1: + name = "xtal_temp"; + break; + case 2: + name = "pcb_temp"; + break; + case 3: + name = "bat_temp"; + break; + case 4: + name = "bat_ctrl"; + break; + case 5: + name = "ab5500"; + break; + default: + return -EINVAL; + } + return sprintf(buf, "%s\n", name); +} + +static int temp_shutdown_trig(int mux) +{ + pm_power_off(); + return 0; +} + +static int ab5500_temp_shutdown_auto(struct abx500_temp *data) +{ + int ret; + struct adc_auto_input *auto_ip; + + auto_ip = kzalloc(sizeof(struct adc_auto_input), GFP_KERNEL); + if (!auto_ip) { + dev_err(&data->pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + auto_ip->mux = DIE_TEMP; + auto_ip->freq = MS500; + auto_ip->min = SHUTDOWN_AUTO_MIN_LIMIT; + auto_ip->max = SHUTDOWN_AUTO_MAX_LIMIT; + auto_ip->auto_adc_callback = temp_shutdown_trig; + data->gpadc_auto = auto_ip; + ret = ab5500_gpadc_convert_auto(data->ab5500_gpadc, + data->gpadc_auto); + if (ret < 0) + kfree(auto_ip); + + return ret; +} + +static int ab5500_is_visible(struct attribute *attr, int n) +{ + return attr->mode; +} + +static int ab5500_temp_irq_handler(int irq, struct abx500_temp *data) +{ + /* + * Make sure the magic numbers below corresponds to the node + * used for AB5500 thermal warning from HW. + */ + mutex_lock(&data->lock); + data->crit_alarm[4] = 1; + mutex_unlock(&data->lock); + sysfs_notify(&data->pdev->dev.kobj, NULL, "temp5_crit_alarm"); + dev_info(&data->pdev->dev, "ABX500 thermal warning," + " power off system now!\n"); + return 0; +} + +int __init ab5500_hwmon_init(struct abx500_temp *data) +{ + int err; + + data->ab5500_gpadc = ab5500_gpadc_get("ab5500-adc.0"); + if (IS_ERR(data->ab5500_gpadc)) + return PTR_ERR(data->ab5500_gpadc); + + data->ab5500_btemp = ab5500_btemp_get(); + if (IS_ERR(data->ab5500_btemp)) + return PTR_ERR(data->ab5500_btemp); + + err = ab5500_temp_shutdown_auto(data); + if (err < 0) { + dev_err(&data->pdev->dev, "Failed to register" + " auto trigger(%d)\n", err); + return err; + } + + /* + * Setup HW defined data. + * + * Reference hardware (HREF): + * + * XTAL_TEMP, PCB_TEMP, BTEMP_BALL refer to millivolts and + * BAT_CTRL and DIE_TEMP refer to millidegrees + * + * Make sure indexes correspond to the attribute indexes + * used when calling SENSOR_DEVICE_ATRR + */ + data->gpadc_addr[0] = XTAL_TEMP; + data->gpadc_addr[1] = PCB_TEMP; + data->gpadc_addr[2] = BTEMP_BALL; + data->gpadc_addr[3] = BAT_CTRL; + data->gpadc_addr[4] = DIE_TEMP; + data->monitored_sensors = NUM_MONITORED_SENSORS; + + data->ops.read_sensor = ab5500_read_sensor; + data->ops.irq_handler = ab5500_temp_irq_handler; + data->ops.show_name = ab5500_show_name; + data->ops.show_label = ab5500_show_label; + data->ops.is_visible = ab5500_is_visible; + + return 0; +} diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c new file mode 100644 index 00000000000..c6694206b4d --- /dev/null +++ b/drivers/hwmon/ab8500.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Martin Persson <martin.persson@stericsson.com> for + * ST-Ericsson. + * License terms: GNU Gereral Public License (GPL) version 2 + * + * Note: + * + * If/when the AB8500 thermal warning temperature is reached (threshold + * cannot be changed by SW), an interrupt is set and the driver + * notifies user space via a sysfs event. If a shut down is not + * triggered by user space within a certain time frame, + * pm_power off is called. + * + * If/when AB8500 thermal shutdown temperature is reached a hardware + * shutdown of the AB8500 will occur. + */ + +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/sysfs.h> +#include <linux/hwmon-sysfs.h> +#include <linux/platform_device.h> +#include <linux/mfd/ab8500/gpadc.h> +#include <linux/mfd/ab8500/bm.h> +#include "abx500.h" +#include <asm/mach-types.h> + +#define DEFAULT_POWER_OFF_DELAY 10000 + +/* + * The driver monitors GPADC - ADC_AUX1, ADC_AUX2, BTEMP_BALL + * and BAT_CTRL. + */ +#define NUM_MONITORED_SENSORS 4 + +static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor) +{ + int val; + /* + * Special treatment for the BAT_CTRL node, since this + * temperature measurement is more complex than just + * an ADC readout + */ + if (sensor == BAT_CTRL) + val = ab8500_btemp_get_batctrl_temp(data->ab8500_btemp); + else + val = ab8500_gpadc_convert(data->ab8500_gpadc, sensor); + + return val; +} + +static void ab8500_thermal_power_off(struct work_struct *work) +{ + struct abx500_temp *data = container_of(work, struct abx500_temp, + power_off_work.work); + + dev_warn(&data->pdev->dev, "Power off due to AB8500 thermal warning\n"); + pm_power_off(); +} + +static ssize_t ab8500_show_name(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "ab8500\n"); +} + +static ssize_t ab8500_show_label(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + char *name; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int index = attr->index; + + /* + * Make sure these labels correspond to the attribute indexes + * used when calling SENSOR_DEVICE_ATRR. + * Temperature sensors outside ab8500 (read via GPADC) are marked + * with prefix ext_ + */ + switch (index) { + case 1: + name = "ext_rtc_xtal"; + break; + case 2: + name = "ext_db8500"; + break; + case 3: + name = "bat_temp"; + break; + case 4: + name = "bat_ctrl"; + break; + case 5: + name = "ab8500"; + break; + default: + return -EINVAL; + } + return sprintf(buf, "%s\n", name); +} + +static int ab8500_is_visible(struct attribute *attr, int n) +{ + if (!strcmp(attr->name, "temp5_input") || + !strcmp(attr->name, "temp5_min") || + !strcmp(attr->name, "temp5_max") || + !strcmp(attr->name, "temp5_max_hyst") || + !strcmp(attr->name, "temp5_min_alarm") || + !strcmp(attr->name, "temp5_max_alarm") || + !strcmp(attr->name, "temp5_max_hyst_alarm")) + return 0; + + return attr->mode; +} + +static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data) +{ + unsigned long delay_in_jiffies; + /* + * Make sure the magic numbers below corresponds to the node + * used for AB8500 thermal warning from HW. + */ + mutex_lock(&data->lock); + data->crit_alarm[4] = 1; + mutex_unlock(&data->lock); + + hwmon_notify(data->crit_alarm[4], NULL); + sysfs_notify(&data->pdev->dev.kobj, NULL, "temp5_crit_alarm"); + dev_info(&data->pdev->dev, "AB8500 thermal warning," + " power off in %lu s\n", data->power_off_delay); + delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); + schedule_delayed_work(&data->power_off_work, delay_in_jiffies); + return 0; +} + +int __init ab8500_hwmon_init(struct abx500_temp *data) +{ + data->ab8500_gpadc = ab8500_gpadc_get(); + if (IS_ERR(data->ab8500_gpadc)) + return PTR_ERR(data->ab8500_gpadc); + + data->ab8500_btemp = ab8500_btemp_get(); + if (IS_ERR(data->ab8500_btemp)) + return PTR_ERR(data->ab8500_btemp); + + INIT_DELAYED_WORK(&data->power_off_work, ab8500_thermal_power_off); + + /* + * Setup HW defined data. + * + * Reference hardware (HREF): + * + * GPADC - ADC_AUX1, connected to NTC R2148 next to RTC_XTAL on HREF + * GPADC - ADC_AUX2, connected to NTC R2150 near DB8500 on HREF + * Hence, temp#_min/max/max_hyst refer to millivolts and not + * millidegrees + * This is not the case for BAT_CTRL where millidegrees is used + * + * HREF HW does not support reading AB8500 temperature. BUT an + * AB8500 IRQ will be launched if die crit temp limit is reached. + * + * Make sure indexes correspond to the attribute indexes + * used when calling SENSOR_DEVICE_ATRR + */ + data->gpadc_addr[0] = ADC_AUX1; + data->gpadc_addr[1] = ADC_AUX2; + data->gpadc_addr[2] = BTEMP_BALL; + data->gpadc_addr[3] = BAT_CTRL; + data->gpadc_addr[4] = DIE_TEMP; + data->power_off_delay = DEFAULT_POWER_OFF_DELAY; + data->monitored_sensors = NUM_MONITORED_SENSORS; + + data->ops.read_sensor = ab8500_read_sensor; + data->ops.irq_handler = ab8500_temp_irq_handler; + data->ops.show_name = ab8500_show_name; + data->ops.show_label = ab8500_show_label; + data->ops.is_visible = ab8500_is_visible; + + return 0; +} diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c new file mode 100644 index 00000000000..de4e6280b4b --- /dev/null +++ b/drivers/hwmon/abx500.c @@ -0,0 +1,698 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Martin Persson <martin.persson@stericsson.com> for + * ST-Ericsson. + * License terms: GNU Gereral Public License (GPL) version 2 + * + * Note: + * + * ABX500 does not provide auto ADC, so to monitor the required + * temperatures, a periodic work is used. It is more important + * to not wake up the CPU than to perform this job, hence the use + * of a deferred delay. + * + * A deferred delay for thermal monitor is considered safe because: + * If the chip gets too hot during a sleep state it's most likely + * due to external factors, such as the surrounding temperature. + * I.e. no SW decisions will make any difference. + * + * If/when the ABX500 thermal warning temperature is reached (threshold + * cannot be changed by SW), an interrupt is set and the driver + * notifies user space via a sysfs event. + * + * If/when ABX500 thermal shutdown temperature is reached a hardware + * shutdown of the ABX500 will occur. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/hwmon.h> +#include <linux/sysfs.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/jiffies.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <asm/mach-types.h> + +#include "abx500.h" + +#define DEFAULT_MONITOR_DELAY 1000 + +/* + * Thresholds are considered inactive if set to 0. + * To avoid confusion for user space applications, + * the temp monitor delay is set to 0 if all thresholds + * are 0. + */ +static bool find_active_thresholds(struct abx500_temp *data) +{ + int i; + for (i = 0; i < data->monitored_sensors; i++) + if (data->max[i] != 0 || data->max_hyst[i] != 0 + || data->min[i] != 0) + return true; + + dev_dbg(&data->pdev->dev, "No active thresholds," + "cancel deferred job (if it exists)" + "and reset temp monitor delay\n"); + cancel_delayed_work_sync(&data->work); + return false; +} + +static inline void schedule_monitor(struct abx500_temp *data) +{ + unsigned long delay_in_jiffies; + delay_in_jiffies = msecs_to_jiffies(data->gpadc_monitor_delay); + schedule_delayed_work(&data->work, delay_in_jiffies); +} + +static inline void gpadc_monitor_exit(struct abx500_temp *data) +{ + cancel_delayed_work_sync(&data->work); +} + +static void gpadc_monitor(struct work_struct *work) +{ + unsigned long delay_in_jiffies; + int val, i, ret; + /* Container for alarm node name */ + char alarm_node[30]; + + bool updated_min_alarm = false; + bool updated_max_alarm = false; + bool updated_max_hyst_alarm = false; + struct abx500_temp *data = container_of(work, struct abx500_temp, + work.work); + + for (i = 0; i < data->monitored_sensors; i++) { + /* Thresholds are considered inactive if set to 0 */ + if (data->max[i] == 0 && data->max_hyst[i] == 0 + && data->min[i] == 0) + continue; + + val = data->ops.read_sensor(data, data->gpadc_addr[i]); + if (val < 0) { + dev_err(&data->pdev->dev, "GPADC read failed\n"); + continue; + } + + mutex_lock(&data->lock); + if (data->min[i] != 0) { + if (val < data->min[i]) { + if (data->min_alarm[i] == 0) { + data->min_alarm[i] = 1; + updated_min_alarm = true; + } + } else { + if (data->min_alarm[i] == 1) { + data->min_alarm[i] = 0; + updated_min_alarm = true; + } + } + + } + if (data->max[i] != 0) { + if (val > data->max[i]) { + if (data->max_alarm[i] == 0) { + data->max_alarm[i] = 1; + updated_max_alarm = true; + } + } else { + if (data->max_alarm[i] == 1) { + data->max_alarm[i] = 0; + updated_max_alarm = true; + } + } + + } + if (data->max_hyst[i] != 0) { + if (val > data->max_hyst[i]) { + if (data->max_hyst_alarm[i] == 0) { + data->max_hyst_alarm[i] = 1; + updated_max_hyst_alarm = true; + } + } else { + if (data->max_hyst_alarm[i] == 1) { + data->max_hyst_alarm[i] = 0; + updated_max_hyst_alarm = true; + } + } + } + mutex_unlock(&data->lock); + + /* hwmon attr index starts at 1, thus "i+1" below */ + if (updated_min_alarm) { + ret = snprintf(alarm_node, 16, "temp%d_min_alarm", + (i + 1)); + if (ret < 0) { + dev_err(&data->pdev->dev, + "Unable to update alarm node (%d)", + ret); + break; + } + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + if (updated_max_alarm) { + ret = snprintf(alarm_node, 16, "temp%d_max_alarm", + (i + 1)); + if (ret < 0) { + dev_err(&data->pdev->dev, + "Unable to update alarm node (%d)", + ret); + break; + } + hwmon_notify(data->max_alarm[i], NULL); + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + if (updated_max_hyst_alarm) { + ret = snprintf(alarm_node, 21, "temp%d_max_hyst_alarm", + (i + 1)); + if (ret < 0) { + dev_err(&data->pdev->dev, + "Unable to update alarm node (%d)", + ret); + break; + } + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + } + delay_in_jiffies = msecs_to_jiffies(data->gpadc_monitor_delay); + schedule_delayed_work(&data->work, delay_in_jiffies); +} + +static ssize_t set_temp_monitor_delay(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int res; + unsigned long delay_in_s; + struct abx500_temp *data = dev_get_drvdata(dev); + + res = strict_strtoul(buf, 10, &delay_in_s); + if (res < 0) + return res; + + mutex_lock(&data->lock); + data->gpadc_monitor_delay = delay_in_s * 1000; + + if (find_active_thresholds(data)) + schedule_monitor(data); + + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int res; + unsigned long delay_in_s; + struct abx500_temp *data = dev_get_drvdata(dev); + + res = strict_strtoul(buf, 10, &delay_in_s); + if (res < 0) + return res; + + mutex_lock(&data->lock); + data->power_off_delay = delay_in_s * 1000; + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_temp_monitor_delay(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + /* return time in s, not ms */ + return sprintf(buf, "%lu\n", (data->gpadc_monitor_delay) / 1000); +} + +static ssize_t show_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + /* return time in s, not ms */ + return sprintf(buf, "%lu\n", (data->power_off_delay) / 1000); +} + +/* HWMON sysfs interface */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + /* + * To avoid confusion between sensor label and chip name, the function + * "show_label" is not used to return the chip name. + */ + struct abx500_temp *data = dev_get_drvdata(dev); + return data->ops.show_name(dev, devattr, buf); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + return data->ops.show_label(dev, devattr, buf); +} + +static ssize_t show_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + u8 gpadc_addr = data->gpadc_addr[attr->index - 1]; + + val = data->ops.read_sensor(data, gpadc_addr); + if (val < 0) + dev_err(&data->pdev->dev, "GPADC read failed\n"); + + return sprintf(buf, "%d\n", val); +} + +/* set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + /* + * Threshold is considered inactive if set to 0 + * hwmon attr index starts at 1, thus "attr->index-1" below + */ + if (val == 0) + data->min_alarm[attr->index - 1] = 0; + + data->min[attr->index - 1] = val; + + if (val == 0) + (void) find_active_thresholds(data); + else + schedule_monitor(data); + + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + /* + * Threshold is considered inactive if set to 0 + * hwmon attr index starts at 1, thus "attr->index-1" below + */ + if (val == 0) + data->max_alarm[attr->index - 1] = 0; + + data->max[attr->index - 1] = val; + + if (val == 0) + (void) find_active_thresholds(data); + else + schedule_monitor(data); + + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_max_hyst(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + /* + * Threshold is considered inactive if set to 0 + * hwmon attr index starts at 1, thus "attr->index-1" below + */ + if (val == 0) + data->max_hyst_alarm[attr->index - 1] = 0; + + data->max_hyst[attr->index - 1] = val; + + if (val == 0) + (void) find_active_thresholds(data); + else + schedule_monitor(data); + + mutex_unlock(&data->lock); + + return count; +} + +/* + * show functions (RO nodes) + */ +static ssize_t show_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->min[attr->index - 1]); +} + +static ssize_t show_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max[attr->index - 1]); +} + +static ssize_t show_max_hyst(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max_hyst[attr->index - 1]); +} + +/* Alarms */ +static ssize_t show_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->min_alarm[attr->index - 1]); +} + +static ssize_t show_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max_alarm[attr->index - 1]); +} + +static ssize_t show_max_hyst_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max_hyst_alarm[attr->index - 1]); +} + +static ssize_t show_crit_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->crit_alarm[attr->index - 1]); +} + +static mode_t abx500_attrs_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct abx500_temp *data = dev_get_drvdata(dev); + return data->ops.is_visible(a, n); +} + +static SENSOR_DEVICE_ATTR(temp_monitor_delay, S_IRUGO | S_IWUSR, + show_temp_monitor_delay, set_temp_monitor_delay, 0); +static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, + show_temp_power_off_delay, + set_temp_power_off_delay, 0); + +/* Chip name, required by hwmon*/ +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); + +/* GPADC - SENSOR1 */ +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 1); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 1); + +/* GPADC - SENSOR2 */ +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min, set_min, 2); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max, set_max, 2); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 2); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_min_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_max_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 2); + +/* GPADC - SENSOR3 */ +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_min, set_min, 3); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_max, set_max, 3); +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 3); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_min_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_max_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 3); + +/* GPADC - SENSOR4 */ +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_input, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_min, set_min, 4); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_max, set_max, 4); +static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 4); +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_min_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_max_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 4); + +/* GPADC - SENSOR5 */ +static SENSOR_DEVICE_ATTR(temp5_label, S_IRUGO, show_label, NULL, 5); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_input, NULL, 5); +static SENSOR_DEVICE_ATTR(temp5_min, S_IWUSR | S_IRUGO, show_min, set_min, 4); +static SENSOR_DEVICE_ATTR(temp5_max, S_IWUSR | S_IRUGO, show_max, set_max, 4); +static SENSOR_DEVICE_ATTR(temp5_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 4); +static SENSOR_DEVICE_ATTR(temp5_min_alarm, S_IRUGO, show_min_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO, show_max_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp5_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp5_crit_alarm, S_IRUGO, + show_crit_alarm, NULL, 5); + +struct attribute *abx500_temp_attributes[] = { + &sensor_dev_attr_name.dev_attr.attr, + &sensor_dev_attr_temp_monitor_delay.dev_attr.attr, + &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, + /* GPADC SENSOR1 */ + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst_alarm.dev_attr.attr, + /* GPADC SENSOR2 */ + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst_alarm.dev_attr.attr, + /* GPADC SENSOR3 */ + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst_alarm.dev_attr.attr, + /* GPADC SENSOR4 */ + &sensor_dev_attr_temp4_label.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst_alarm.dev_attr.attr, + /* GPADC SENSOR5*/ + &sensor_dev_attr_temp5_label.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_min.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp5_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp5_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_max_hyst_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group abx500_temp_group = { + .attrs = abx500_temp_attributes, + .is_visible = abx500_attrs_visible, +}; + +static irqreturn_t abx500_temp_irq_handler(int irq, void *irq_data) +{ + struct platform_device *pdev = irq_data; + struct abx500_temp *data = platform_get_drvdata(pdev); + data->ops.irq_handler(irq, data); + return IRQ_HANDLED; +} + +static int setup_irqs(struct platform_device *pdev) +{ + int ret; + int irq = platform_get_irq_byname(pdev, "ABX500_TEMP_WARM"); + + if (irq < 0) + dev_err(&pdev->dev, "Get irq by name failed\n"); + + ret = request_threaded_irq(irq, NULL, abx500_temp_irq_handler, + IRQF_NO_SUSPEND, "abx500-temp", pdev); + if (ret < 0) + dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); + + return ret; +} + +static int __devinit abx500_temp_probe(struct platform_device *pdev) +{ + struct abx500_temp *data; + int err; + + data = kzalloc(sizeof(struct abx500_temp), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->pdev = pdev; + mutex_init(&data->lock); + + /* Chip specific initialization */ + if (!machine_is_u5500()) + err = ab8500_hwmon_init(data); + else + err = ab5500_hwmon_init(data); + if (err < 0) { + dev_err(&pdev->dev, "abx500 init failed"); + goto exit; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit; + } + + INIT_DELAYED_WORK_DEFERRABLE(&data->work, gpadc_monitor); + data->gpadc_monitor_delay = DEFAULT_MONITOR_DELAY; + + platform_set_drvdata(pdev, data); + + err = sysfs_create_group(&pdev->dev.kobj, &abx500_temp_group); + if (err < 0) { + dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); + goto exit_platform_data; + } + + err = setup_irqs(pdev); + if (err < 0) { + dev_err(&pdev->dev, "irq setup failed (%d)\n", err); + goto exit_sysfs_group; + } + return 0; + +exit_sysfs_group: + sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); +exit_platform_data: + hwmon_device_unregister(data->hwmon_dev); + platform_set_drvdata(pdev, NULL); +exit: + kfree(data->gpadc_auto); + kfree(data); + return err; +} + +static int __devexit abx500_temp_remove(struct platform_device *pdev) +{ + struct abx500_temp *data = platform_get_drvdata(pdev); + + gpadc_monitor_exit(data); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); + platform_set_drvdata(pdev, NULL); + kfree(data->gpadc_auto); + kfree(data); + return 0; +} + +/* No action required in suspend/resume, thus the lack of functions */ +static struct platform_driver abx500_temp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "abx500-temp", + }, + .probe = abx500_temp_probe, + .remove = __devexit_p(abx500_temp_remove), +}; + +static int __init abx500_temp_init(void) +{ + return platform_driver_register(&abx500_temp_driver); +} + +static void __exit abx500_temp_exit(void) +{ + platform_driver_unregister(&abx500_temp_driver); +} + +MODULE_AUTHOR("Martin Persson <martin.persson@stericsson.com>"); +MODULE_DESCRIPTION("ABX500 temperature driver"); +MODULE_LICENSE("GPL"); + +module_init(abx500_temp_init) +module_exit(abx500_temp_exit) diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h new file mode 100644 index 00000000000..9fe28dac28f --- /dev/null +++ b/drivers/hwmon/abx500.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * License terms: GNU General Public License v2 + * Author: Martin Persson <martin.persson@stericsson.com> + */ + +#ifndef _ABX500_H +#define _ABX500_H + +#define NUM_SENSORS 5 + +struct ab8500_gpadc; +struct ab5500_gpadc; +struct ab8500_btemp; +struct ab5500_btemp; +struct adc_auto_input; +struct abx500_temp; + +/** + * struct abx500_temp_ops - abx500 chip specific ops + * @read_sensor: reads gpadc output + * @irq_handler: irq handler + * @show_name: hwmon device name + * @show_label: hwmon attribute label + * @is_visible: is attribute visible + */ +struct abx500_temp_ops { + int (*read_sensor)(struct abx500_temp *, u8); + int (*irq_handler)(int, struct abx500_temp *); + ssize_t (*show_name)(struct device *, + struct device_attribute *, char *); + ssize_t (*show_label) (struct device *, + struct device_attribute *, char *); + int (*is_visible)(struct attribute *, int); +}; + +/** + * struct abx500_temp - representation of temp mon device + * @pdev: platform device + * @hwmon_dev: hwmon device + * @ab8500_gpadc: gpadc interface for ab8500 + * @ab5500_gpadc: gpadc interface for ab5500 + * @btemp: battery temperature interface for ab8500 + * @adc_auto_input: gpadc auto trigger + * @gpadc_addr: gpadc channel address + * @temp: sensor temperature input value + * @min: sensor temperature min value + * @max: sensor temperature max value + * @max_hyst: sensor temperature hysteresis value for max limit + * @crit: sensor temperature critical value + * @min_alarm: sensor temperature min alarm + * @max_alarm: sensor temperature max alarm + * @max_hyst_alarm: sensor temperature hysteresis alarm + * @crit_alarm: sensor temperature critical value alarm + * @work: delayed work scheduled to monitor temperature periodically + * @power_off_work: delayed work scheduled to power off the system + when critical temperature is reached + * @lock: mutex + * @gpadc_monitor_delay: delay between temperature readings in ms + * @power_off_delay: delay before power off in ms + * @monitored_sensors: number of monitored sensors + */ +struct abx500_temp { + struct platform_device *pdev; + struct device *hwmon_dev; + struct ab8500_gpadc *ab8500_gpadc; + struct ab5500_gpadc *ab5500_gpadc; + struct ab8500_btemp *ab8500_btemp; + struct ab5500_btemp *ab5500_btemp; + struct adc_auto_input *gpadc_auto; + struct abx500_temp_ops ops; + u8 gpadc_addr[NUM_SENSORS]; + unsigned long temp[NUM_SENSORS]; + unsigned long min[NUM_SENSORS]; + unsigned long max[NUM_SENSORS]; + unsigned long max_hyst[NUM_SENSORS]; + unsigned long crit[NUM_SENSORS]; + unsigned long min_alarm[NUM_SENSORS]; + unsigned long max_alarm[NUM_SENSORS]; + unsigned long max_hyst_alarm[NUM_SENSORS]; + unsigned long crit_alarm[NUM_SENSORS]; + struct delayed_work work; + struct delayed_work power_off_work; + struct mutex lock; + /* Delay (ms) between temperature readings */ + unsigned long gpadc_monitor_delay; + /* Delay (ms) before power off */ + unsigned long power_off_delay; + int monitored_sensors; +}; + +int ab8500_hwmon_init(struct abx500_temp *data) __init; +int ab5500_hwmon_init(struct abx500_temp *data) __init; + +#endif /* _ABX500_H */ diff --git a/drivers/hwmon/dbx500.c b/drivers/hwmon/dbx500.c new file mode 100644 index 00000000000..c034b48f8dd --- /dev/null +++ b/drivers/hwmon/dbx500.c @@ -0,0 +1,402 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + * + * Author: WenHai Fang <wenhai.h.fang@stericsson.com> for + * ST-Ericsson. + * License terms: GNU Gereral Public License (GPL) version 2 + * + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <linux/hwmon.h> +#include <linux/sysfs.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/jiffies.h> +#include <linux/mutex.h> +#include <linux/pm.h> +#include <linux/io.h> +#include <mach/hardware.h> + +/* + * Default measure period to 0xFF x cycle32k + */ +#define DEFAULT_MEASURE_TIME 0xFF + +/* + * Default critical sensor temperature + */ +#define DEFAULT_CRITICAL_TEMP 85 + +/* This driver monitors DB thermal*/ +#define NUM_SENSORS 1 + +struct dbx500_temp { + struct platform_device *pdev; + struct device *hwmon_dev; + unsigned char min[NUM_SENSORS]; + unsigned char max[NUM_SENSORS]; + unsigned char crit[NUM_SENSORS]; + unsigned char min_alarm[NUM_SENSORS]; + unsigned char max_alarm[NUM_SENSORS]; + unsigned short measure_time; + bool monitoring_active; + struct mutex lock; +}; + +static inline void start_temp_monitoring(struct dbx500_temp *data, + const int index) +{ + unsigned int i; + + /* determine if there are any sensors worth monitoring */ + for (i = 0; i < NUM_SENSORS; i++) + if (data->min[i] || data->max[i]) + goto start_monitoring; + + return; + +start_monitoring: + /* kick off the monitor job */ + data->min_alarm[index] = 0; + data->max_alarm[index] = 0; + + (void) prcmu_start_temp_sense(data->measure_time); + data->monitoring_active = true; +} + +static inline void stop_temp_monitoring(struct dbx500_temp *data) +{ + if (data->monitoring_active) { + (void) prcmu_stop_temp_sense(); + data->monitoring_active = false; + } +} + +/* HWMON sysfs interface */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "dbx500\n"); +} + +static ssize_t show_label(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return show_name(dev, devattr, buf); +} + +/* set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + val &= 0xFF; + if (val > data->max[attr->index - 1]) + val = data->max[attr->index - 1]; + + data->min[attr->index - 1] = val; + + stop_temp_monitoring(data); + + (void) prcmu_config_hotmon(data->min[attr->index - 1], + data->max[attr->index - 1]); + + start_temp_monitoring(data, (attr->index - 1)); + + mutex_unlock(&data->lock); + return count; +} + +static ssize_t set_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + val &= 0xFF; + if (val < data->min[attr->index - 1]) + val = data->min[attr->index - 1]; + + data->max[attr->index - 1] = val; + + stop_temp_monitoring(data); + + (void) prcmu_config_hotmon(data->min[attr->index - 1], + data->max[attr->index - 1]); + + start_temp_monitoring(data, (attr->index - 1)); + + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_crit(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + val &= 0xFF; + data->crit[attr->index - 1] = val; + (void) prcmu_config_hotdog(data->crit[attr->index - 1]); + mutex_unlock(&data->lock); + + return count; +} + +/* + * show functions (RO nodes) + * Notice that min/max/crit refer to degrees + */ +static ssize_t show_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->min[attr->index - 1]); +} + +static ssize_t show_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->max[attr->index - 1]); +} + +static ssize_t show_crit(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->crit[attr->index - 1]); +} + +/* Alarms */ +static ssize_t show_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->min_alarm[attr->index - 1]); +} + +static ssize_t show_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->max_alarm[attr->index - 1]); +} + +/* Chip name, required by hwmon*/ +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, + show_crit, set_crit, 1); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); + +static struct attribute *dbx500_temp_attributes[] = { + &sensor_dev_attr_name.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group dbx500_temp_group = { + .attrs = dbx500_temp_attributes, +}; + +static irqreturn_t prcmu_hotmon_low_irq_handler(int irq, void *irq_data) +{ + struct platform_device *pdev = irq_data; + struct dbx500_temp *data = platform_get_drvdata(pdev); + + mutex_lock(&data->lock); + data->min_alarm[0] = 1; + mutex_unlock(&data->lock); + + sysfs_notify(&pdev->dev.kobj, NULL, "temp1_min_alarm"); + dev_dbg(&pdev->dev, "DBX500 thermal low warning\n"); + return IRQ_HANDLED; +} + +static irqreturn_t prcmu_hotmon_high_irq_handler(int irq, void *irq_data) +{ + struct platform_device *pdev = irq_data; + struct dbx500_temp *data = platform_get_drvdata(pdev); + + mutex_lock(&data->lock); + data->max_alarm[0] = 1; + mutex_unlock(&data->lock); + + hwmon_notify(data->max_alarm[0], NULL); + sysfs_notify(&pdev->dev.kobj, NULL, "temp1_max_alarm"); + + return IRQ_HANDLED; +} + +static int __devinit dbx500_temp_probe(struct platform_device *pdev) +{ + struct dbx500_temp *data; + int err = 0, i; + int irq; + + dev_dbg(&pdev->dev, "dbx500_temp: Function dbx500_temp_probe.\n"); + + data = kzalloc(sizeof(struct dbx500_temp), GFP_KERNEL); + if (!data) + return -ENOMEM; + + irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW"); + if (irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed\n"); + goto exit; + } + + err = request_threaded_irq(irq, NULL, + prcmu_hotmon_low_irq_handler, + IRQF_NO_SUSPEND, + "dbx500_temp_low", pdev); + if (err < 0) { + dev_err(&pdev->dev, "dbx500: Failed allocate HOTMON_LOW.\n"); + goto exit; + } else { + dev_dbg(&pdev->dev, "dbx500: Succeed allocate HOTMON_LOW.\n"); + } + + irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); + if (irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed\n"); + goto exit; + } + + err = request_threaded_irq(irq, NULL, + prcmu_hotmon_high_irq_handler, + IRQF_NO_SUSPEND, + "dbx500_temp_high", pdev); + if (err < 0) { + dev_err(&pdev->dev, "dbx500: Failed allocate HOTMON_HIGH.\n"); + goto exit; + } else { + dev_dbg(&pdev->dev, "dbx500: Succeed allocate HOTMON_HIGH.\n"); + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit; + } + + for (i = 0; i < NUM_SENSORS; i++) { + data->min[i] = 0; + data->max[i] = 0; + data->crit[i] = DEFAULT_CRITICAL_TEMP; + data->min_alarm[i] = 0; + data->max_alarm[i] = 0; + } + + mutex_init(&data->lock); + + data->pdev = pdev; + data->measure_time = DEFAULT_MEASURE_TIME; + data->monitoring_active = false; + + /* set PRCMU to disable platform when we get to the critical temp */ + (void) prcmu_config_hotdog(DEFAULT_CRITICAL_TEMP); + + platform_set_drvdata(pdev, data); + + err = sysfs_create_group(&pdev->dev.kobj, &dbx500_temp_group); + if (err < 0) { + dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); + goto exit_platform_data; + } + + return 0; + +exit_platform_data: + platform_set_drvdata(pdev, NULL); +exit: + kfree(data); + return err; +} + +static int __devexit dbx500_temp_remove(struct platform_device *pdev) +{ + struct dbx500_temp *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &dbx500_temp_group); + platform_set_drvdata(pdev, NULL); + kfree(data); + return 0; +} + +/* No action required in suspend/resume, thus the lack of functions */ +static struct platform_driver dbx500_temp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "dbx500_temp", + }, + .probe = dbx500_temp_probe, + .remove = __devexit_p(dbx500_temp_remove), +}; + +static int __init dbx500_temp_init(void) +{ + return platform_driver_register(&dbx500_temp_driver); +} + +static void __exit dbx500_temp_exit(void) +{ + platform_driver_unregister(&dbx500_temp_driver); +} + +MODULE_AUTHOR("WenHai Fang <wenhai.h.fang@stericsson.com>"); +MODULE_DESCRIPTION("DBX500 temperature driver"); +MODULE_LICENSE("GPL"); + +module_init(dbx500_temp_init) +module_exit(dbx500_temp_exit) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index a61e7815a2a..b3c62f84b0b 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -21,6 +21,7 @@ #include <linux/gfp.h> #include <linux/spinlock.h> #include <linux/pci.h> +#include <linux/notifier.h> #define HWMON_ID_PREFIX "hwmon" #define HWMON_ID_FORMAT HWMON_ID_PREFIX "%d" @@ -29,7 +30,7 @@ static struct class *hwmon_class; static DEFINE_IDR(hwmon_idr); static DEFINE_SPINLOCK(idr_lock); - +static BLOCKING_NOTIFIER_HEAD(hwmon_notifier_list); /** * hwmon_device_register - register w/ hwmon * @dev: the device to register @@ -89,6 +90,24 @@ void hwmon_device_unregister(struct device *dev) "hwmon_device_unregister() failed: bad class ID!\n"); } +int hwmon_notifier_register(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&hwmon_notifier_list, nb); +} +EXPORT_SYMBOL(hwmon_notifier_register); + +int hwmon_notifier_unregister(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&hwmon_notifier_list, nb); +} +EXPORT_SYMBOL(hwmon_notifier_unregister); + +void hwmon_notify(unsigned long val, void *v) +{ + blocking_notifier_call_chain(&hwmon_notifier_list, val, v); +} +EXPORT_SYMBOL(hwmon_notify); + static void __init hwmon_pci_quirks(void) { #if defined CONFIG_X86 && defined CONFIG_PCI diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index b228e09c5d0..7f9e4db1031 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -63,11 +63,11 @@ /* Master controller (MCR) register */ #define I2C_MCR_OP (0x1 << 0) /* Operation */ #define I2C_MCR_A7 (0x7f << 1) /* 7-bit address */ -#define I2C_MCR_EA10 (0x7 << 8) /* 10-bit Extended address */ +#define I2C_MCR_EA10 (0x7 << 8) /* 10-bit Extended address */ #define I2C_MCR_SB (0x1 << 11) /* Extended address */ #define I2C_MCR_AM (0x3 << 12) /* Address type */ -#define I2C_MCR_STOP (0x1 << 14) /* Stop condition */ -#define I2C_MCR_LENGTH (0x7ff << 15) /* Transaction length */ +#define I2C_MCR_STOP (0x1 << 14) /* Stop condition */ +#define I2C_MCR_LENGTH (0x7ff << 15) /* Transaction length */ /* Status register (SR) */ #define I2C_SR_OP (0x3 << 0) /* Operation */ @@ -77,7 +77,7 @@ #define I2C_SR_LENGTH (0x7ff << 9) /* Transfer length */ /* Interrupt mask set/clear (IMSCR) bits */ -#define I2C_IT_TXFE (0x1 << 0) +#define I2C_IT_TXFE (0x1 << 0) #define I2C_IT_TXFNE (0x1 << 1) #define I2C_IT_TXFF (0x1 << 2) #define I2C_IT_TXFOVR (0x1 << 3) @@ -151,15 +151,15 @@ struct i2c_nmk_client { */ struct nmk_i2c_dev { struct platform_device *pdev; - struct i2c_adapter adap; - int irq; + struct i2c_adapter adap; + int irq; void __iomem *virtbase; struct clk *clk; struct nmk_i2c_controller cfg; struct i2c_nmk_client cli; - int stop; + int stop; struct completion xfer_complete; - int result; + int result; struct regulator *regulator; bool busy; }; @@ -217,8 +217,9 @@ static int flush_i2c_fifo(struct nmk_i2c_dev *dev) } } - dev_err(&dev->pdev->dev, "flushing operation timed out " - "giving up after %d attempts", LOOP_ATTEMPTS); + dev_err(&dev->pdev->dev, + "flushing operation timed out giving up after %d attempts", + LOOP_ATTEMPTS); return -ETIMEDOUT; } @@ -270,7 +271,7 @@ exit: } /* enable peripheral, master mode operation */ -#define DEFAULT_I2C_REG_CR ((1 << 1) | I2C_CR_PE) +#define DEFAULT_I2C_REG_CR ((1 << 1) | I2C_CR_PE) /** * load_i2c_mcr_reg() - load the MCR register @@ -363,8 +364,8 @@ static void setup_i2c_controller(struct nmk_i2c_dev *dev) * and high speed (up to 3.4 Mb/s) */ if (dev->cfg.sm > I2C_FREQ_MODE_FAST) { - dev_err(&dev->pdev->dev, "do not support this mode " - "defaulting to std. mode\n"); + dev_err(&dev->pdev->dev, + "do not support this mode defaulting to std. mode\n"); brcr2 = i2c_clk/(100000 * 2) & 0xffff; writel((brcr1 | brcr2), dev->virtbase + I2C_BRCR); writel(I2C_FREQ_MODE_STANDARD << 4, @@ -430,7 +431,7 @@ static int read_i2c(struct nmk_i2c_dev *dev) if (timeout == 0) { /* Controller timed out */ - dev_err(&dev->pdev->dev, "read from slave 0x%x timed out\n", + dev_err(&dev->pdev->dev, "Read from Slave 0x%x timed out\n", dev->cli.slave_adr); status = -ETIMEDOUT; } @@ -517,7 +518,7 @@ static int write_i2c(struct nmk_i2c_dev *dev) if (timeout == 0) { /* Controller timed out */ - dev_err(&dev->pdev->dev, "write to slave 0x%x timed out\n", + dev_err(&dev->pdev->dev, "Write to slave 0x%x timed out\n", dev->cli.slave_adr); status = -ETIMEDOUT; } @@ -556,8 +557,8 @@ static int nmk_i2c_xfer_one(struct nmk_i2c_dev *dev, u16 flags) if (((i2c_sr >> 2) & 0x3) == 0x3) { /* get the abort cause */ cause = (i2c_sr >> 4) & 0x7; - dev_err(&dev->pdev->dev, "%s\n", cause - >= ARRAY_SIZE(abort_causes) ? + dev_err(&dev->pdev->dev, "%s\n", + cause >= ARRAY_SIZE(abort_causes) ? "unknown reason" : abort_causes[cause]); } @@ -582,13 +583,13 @@ static int nmk_i2c_xfer_one(struct nmk_i2c_dev *dev, u16 flags) * * NOTE: * READ TRANSFER : We impose a restriction of the first message to be the - * index message for any read transaction. - * - a no index is coded as '0', - * - 2byte big endian index is coded as '3' - * !!! msg[0].buf holds the actual index. - * This is compatible with generic messages of smbus emulator - * that send a one byte index. - * eg. a I2C transation to read 2 bytes from index 0 + * index message for any read transaction. + * - a no index is coded as '0', + * - 2byte big endian index is coded as '3' + * !!! msg[0].buf holds the actual index. + * This is compatible with generic messages of smbus emulator + * that send a one byte index. + * eg. a I2C transation to read 2 bytes from index 0 * idx = 0; * msg[0].addr = client->addr; * msg[0].flags = 0x0; @@ -633,6 +634,8 @@ static int nmk_i2c_xfer(struct i2c_adapter *i2c_adap, clk_enable(dev->clk); + dev->busy = true; + status = init_hw(dev); if (status) goto out; @@ -644,8 +647,8 @@ static int nmk_i2c_xfer(struct i2c_adapter *i2c_adap, for (i = 0; i < num_msgs; i++) { if (unlikely(msgs[i].flags & I2C_M_TEN)) { - dev_err(&dev->pdev->dev, "10 bit addressing" - "not supported\n"); + dev_err(&dev->pdev->dev, + "10 bit addressing not supported\n"); status = -EINVAL; goto out; @@ -789,8 +792,9 @@ static irqreturn_t i2c_irq_handler(int irq, void *arg) if (dev->cli.count) { dev->result = -EIO; - dev_err(&dev->pdev->dev, "%lu bytes still remain to be" - "xfered\n", dev->cli.count); + dev_err(&dev->pdev->dev, + "%lu bytes still remain to bexfered\n", + dev->cli.count); (void) init_hw(dev); } complete(&dev->xfer_complete); @@ -923,7 +927,7 @@ static int __devinit nmk_i2c_probe(struct platform_device *pdev) } if (request_mem_region(res->start, resource_size(res), - DRIVER_NAME "I/O region") == NULL) { + DRIVER_NAME "I/O region") == NULL) { ret = -EBUSY; goto err_no_region; } @@ -980,8 +984,9 @@ static int __devinit nmk_i2c_probe(struct platform_device *pdev) i2c_set_adapdata(adap, dev); - dev_info(&pdev->dev, "initialize %s on virtual " - "base %p\n", adap->name, dev->virtbase); + dev_info(&pdev->dev, + "initialize %s on virtual base %p\n", + adap->name, dev->virtbase); ret = i2c_add_numbered_adapter(adap); if (ret) { @@ -1044,6 +1049,7 @@ static struct platform_driver nmk_i2c_driver = { }, .probe = nmk_i2c_probe, .remove = __devexit_p(nmk_i2c_remove), + .suspend = nmk_i2c_suspend, }; static int __init nmk_i2c_init(void) diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index b591e726a6f..12ce1177f12 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -50,6 +50,14 @@ config LEDS_LM3530 controlled manually or using PWM input or using ambient light automatically. +config LEDS_AB5500 + tristate "HVLED driver for AB5500" + depends on AB5500_CORE + help + This option enables support for the HVLED in AB5500 + multi function device. Currently Ab5500 v1.0 chip leds + are supported. + config LEDS_LOCOMO tristate "LED Support for Locomo device" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index bbfd2e367dc..d2acb6e35d8 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o +obj-$(CONFIG_LEDS_AB5500) += leds-ab5500.o obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o obj-$(CONFIG_LEDS_AMS_DELTA) += leds-ams-delta.o diff --git a/drivers/leds/leds-ab5500.c b/drivers/leds/leds-ab5500.c new file mode 100644 index 00000000000..294551b1962 --- /dev/null +++ b/drivers/leds/leds-ab5500.c @@ -0,0 +1,811 @@ +/* + * leds-ab5500.c - driver for High Voltage (HV) LED in ST-Ericsson AB5500 chip + * + * Copyright (C) 2011 ST-Ericsson SA. + * + * License Terms: GNU General Public License v2 + * + * Author: Shreshtha Kumar SAHU <shreshthakumar.sahu@stericsson.com> + */ + +/* + * Driver for HVLED in ST-Ericsson AB5500 analog baseband controller + * + * This chip can drive upto 3 leds, of upto 40mA of led sink current. + * These leds can be programmed to blink between two intensities with + * fading delay of half, one or two seconds. + * + * Leds can be controlled via sysfs entries in + * "/sys/class/leds/< red | green | blue >" + * + * For each led, + * + * Modes of operation: + * - manual: echo 0 > fade_auto (default, no auto blinking) + * - auto: echo 1 > fade_auto + * + * Soft scaling delay between two intensities: + * - 1/2 sec: echo 1 > fade_delay + * - 1 sec: echo 2 > fade_delay + * - 2 sec: echo 3 > fade_delay + * + * Possible sequence of operation: + * - continuous glow: set brightness (brt) + * - blink between LED_OFF and LED_FULL: + * set fade delay -> set fade auto + * - blink between previous two brightness (only for LED-1): + * set brt1 -> set brt2 -> set fade auto + * + * Delay can be set in any step, its affect will be seen on switching mode. + * + * Note: Blink/Fade feature is supported in AB5500 v2 onwards + * + */ + +#include <linux/leds.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/leds-ab5500.h> +#include <linux/types.h> + +#include <mach/hardware.h> + +#define AB5500LED_NAME "ab5500-leds" +#define AB5500_LED_MAX 0x03 + +/* Register offsets */ +#define AB5500_LED_REG_ENABLE 0x03 +#define AB5500_LED_FADE_CTRL 0x0D + +/* LED-0 Register Addr. Offsets */ +#define AB5500_LED0_PWM_DUTY 0x01 +#define AB5500_LED0_PWMFREQ 0x02 +#define AB5500_LED0_SINKCTL 0x0A +#define AB5500_LED0_FADE_HI 0x11 +#define AB5500_LED0_FADE_LO 0x17 + +/* LED-1 Register Addr. Offsets */ +#define AB5500_LED1_PWM_DUTY 0x05 +#define AB5500_LED1_PWMFREQ 0x06 +#define AB5500_LED1_SINKCTL 0x0B +#define AB5500_LED1_FADE_HI 0x13 +#define AB5500_LED1_FADE_LO 0x19 + +/* LED-2 Register Addr. Offsets */ +#define AB5500_LED2_PWM_DUTY 0x08 +#define AB5500_LED2_PWMFREQ 0x09 +#define AB5500_LED2_SINKCTL 0x0C +#define AB5500_LED2_FADE_HI 0x15 +#define AB5500_LED2_FADE_LO 0x1B + +/* led-0/1/2 enable bit */ +#define AB5500_LED_ENABLE_MASK 0x04 + +/* led intensity */ +#define AB5500_LED_INTENSITY_OFF 0x0 +#define AB5500_LED_INTENSITY_MAX 0x3FF +#define AB5500_LED_INTENSITY_STEP (AB5500_LED_INTENSITY_MAX/LED_FULL) + +/* pwm frequency */ +#define AB5500_LED_PWMFREQ_MAX 0x0F /* 373.39 @sysclk=26MHz */ +#define AB5500_LED_PWMFREQ_SHIFT 4 + +/* LED sink current control */ +#define AB5500_LED_SINKCURR_MAX 0x0F /* 40mA MAX */ +#define AB5500_LED_SINKCURR_SHIFT 4 + +/* fade Control shift and masks */ +#define AB5500_FADE_DELAY_SHIFT 0x00 +#define AB5500_FADE_MODE_MASK 0x80 +#define AB5500_FADE_DELAY_MASK 0x03 +#define AB5500_FADE_START_MASK 0x04 +#define AB5500_FADE_ON_MASK 0x70 +#define AB5500_LED_FADE_ENABLE(ledid) (0x40 >> (ledid)) + +struct ab5500_led { + u8 id; + u8 max_current; + u16 brt_val; + u16 fade_hi; + u16 fade_lo; + bool led_on; + struct led_classdev led_cdev; + struct work_struct led_work; +}; + +struct ab5500_hvleds { + struct mutex lock; + struct device *dev; + struct ab5500_hvleds_platform_data *pdata; + struct ab5500_led leds[AB5500_HVLEDS_MAX]; + bool hw_fade; + bool fade_auto; + enum ab5500_fade_delay fade_delay; +}; + +static u8 ab5500_led_pwmduty_reg[AB5500_LED_MAX] = { + AB5500_LED0_PWM_DUTY, + AB5500_LED1_PWM_DUTY, + AB5500_LED2_PWM_DUTY, +}; + +static u8 ab5500_led_pwmfreq_reg[AB5500_LED_MAX] = { + AB5500_LED0_PWMFREQ, + AB5500_LED1_PWMFREQ, + AB5500_LED2_PWMFREQ, +}; + +static u8 ab5500_led_sinkctl_reg[AB5500_LED_MAX] = { + AB5500_LED0_SINKCTL, + AB5500_LED1_SINKCTL, + AB5500_LED2_SINKCTL +}; + +static u8 ab5500_led_fade_hi_reg[AB5500_LED_MAX] = { + AB5500_LED0_FADE_HI, + AB5500_LED1_FADE_HI, + AB5500_LED2_FADE_HI, +}; + +static u8 ab5500_led_fade_lo_reg[AB5500_LED_MAX] = { + AB5500_LED0_FADE_LO, + AB5500_LED1_FADE_LO, + AB5500_LED2_FADE_LO, +}; + +#define to_led(_x) container_of(_x, struct ab5500_led, _x) + +static inline struct ab5500_hvleds *led_to_hvleds(struct ab5500_led *led) +{ + return container_of(led, struct ab5500_hvleds, leds[led->id]); +} + +static int ab5500_led_enable(struct ab5500_hvleds *hvleds, + unsigned int led_id) +{ + int ret; + + ret = abx500_mask_and_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id], + AB5500_LED_ENABLE_MASK, + AB5500_LED_ENABLE_MASK); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_pwmduty_reg[led_id], ret); + + return ret; + +} + +static int ab5500_led_start_manual(struct ab5500_hvleds *hvleds) +{ + int ret; + + mutex_lock(&hvleds->lock); + + ret = abx500_mask_and_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + AB5500_LED_FADE_CTRL, AB5500_FADE_START_MASK, + AB5500_FADE_START_MASK); + if (ret < 0) + dev_err(hvleds->dev, "update reg 0x%x failed - %d\n", + AB5500_LED_FADE_CTRL, ret); + + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_disable(struct ab5500_hvleds *hvleds, + unsigned int led_id) +{ + int ret; + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id] - 1, 0); + ret |= abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id], 0); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_pwmduty_reg[led_id], ret); + + return ret; +} + +static int ab5500_led_pwmduty_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, u16 val) +{ + int ret; + u8 val_lsb = val & 0xFF; + u8 val_msb = (val & 0x300) >> 8; + + mutex_lock(&hvleds->lock); + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val = %d\n" + "reg[%d] w val = %d\n", + ab5500_led_pwmduty_reg[led_id] - 1, val_lsb, + ab5500_led_pwmduty_reg[led_id], val_msb); + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id] - 1, val_lsb); + ret |= abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id], val_msb); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_pwmduty_reg[led_id], ret); + + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_pwmfreq_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, u8 val) +{ + int ret; + + val = (val & 0x0F) << AB5500_LED_PWMFREQ_SHIFT; + + mutex_lock(&hvleds->lock); + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val=%d\n", + ab5500_led_pwmfreq_reg[led_id], val); + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmfreq_reg[led_id], val); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_pwmfreq_reg[led_id], ret); + + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_sinkctl_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, u8 val) +{ + int ret; + + if (val > AB5500_LED_SINKCURR_MAX) + val = AB5500_LED_SINKCURR_MAX; + + val = (val << AB5500_LED_SINKCURR_SHIFT); + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val=%d\n", + ab5500_led_sinkctl_reg[led_id], val); + + mutex_lock(&hvleds->lock); + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_sinkctl_reg[led_id], val); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_sinkctl_reg[led_id], ret); + + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_fade_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, bool on, u16 val) +{ + int ret; + int val_lsb = val & 0xFF; + int val_msb = (val & 0x300) >> 8; + u8 *fade_reg; + + if (on) + fade_reg = ab5500_led_fade_hi_reg; + else + fade_reg = ab5500_led_fade_lo_reg; + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val = %d\n" + "reg[%d] w val = %d\n", + fade_reg[led_id] - 1, val_lsb, + fade_reg[led_id], val_msb); + + mutex_lock(&hvleds->lock); + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + fade_reg[led_id] - 1, val_lsb); + ret |= abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + fade_reg[led_id], val_msb); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + fade_reg[led_id], ret); + + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_sinkctl_read(struct ab5500_hvleds *hvleds, + unsigned int led_id) +{ + int ret; + u8 val; + + mutex_lock(&hvleds->lock); + + ret = abx500_get_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_sinkctl_reg[led_id], &val); + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] r failed: %d\n", + ab5500_led_sinkctl_reg[led_id], ret); + mutex_unlock(&hvleds->lock); + return ret; + } + + val = (val & 0xF0) >> AB5500_LED_SINKCURR_SHIFT; + + mutex_unlock(&hvleds->lock); + + return val; +} + +static void ab5500_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brt_val) +{ + struct ab5500_led *led = to_led(led_cdev); + + /* adjust LED_FULL to 10bit range */ + brt_val &= LED_FULL; + led->brt_val = brt_val * AB5500_LED_INTENSITY_STEP; + + schedule_work(&led->led_work); +} + +static void ab5500_led_work(struct work_struct *led_work) +{ + struct ab5500_led *led = to_led(led_work); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + if (led->led_on == true) { + ab5500_led_pwmduty_write(hvleds, led->id, led->brt_val); + if (hvleds->hw_fade && led->brt_val) { + ab5500_led_enable(hvleds, led->id); + ab5500_led_start_manual(hvleds); + } + } +} + +static ssize_t ab5500_led_show_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int led_curr = 0; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ab5500_led *led = to_led(led_cdev); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + led_curr = ab5500_led_sinkctl_read(hvleds, led->id); + + if (led_curr < 0) + return led_curr; + + return sprintf(buf, "%d\n", led_curr); +} + +static ssize_t ab5500_led_store_current(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int ret; + unsigned long led_curr; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ab5500_led *led = to_led(led_cdev); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + if (strict_strtoul(buf, 0, &led_curr)) + return -EINVAL; + + if (led_curr > led->max_current) + led_curr = led->max_current; + + ret = ab5500_led_sinkctl_write(hvleds, led->id, led_curr); + if (ret < 0) + return ret; + + return len; +} + +static ssize_t ab5500_led_store_fade_auto(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int ret; + u8 fade_ctrl = 0; + unsigned long fade_auto; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ab5500_led *led = to_led(led_cdev); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + if (strict_strtoul(buf, 0, &fade_auto)) + return -EINVAL; + + if (fade_auto > 1) { + dev_err(hvleds->dev, "invalid mode\n"); + return -EINVAL; + } + + mutex_lock(&hvleds->lock); + + ret = abx500_get_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + AB5500_LED_FADE_CTRL, &fade_ctrl); + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + AB5500_LED_FADE_CTRL, ret); + goto unlock_and_return; + } + + /* manual mode */ + if (fade_auto == false) { + fade_ctrl &= ~(AB5500_LED_FADE_ENABLE(led->id)); + if (!(fade_ctrl & AB5500_FADE_ON_MASK)) + fade_ctrl = 0; + + ret = ab5500_led_disable(hvleds, led->id); + if (ret < 0) + goto unlock_and_return; + } else { + /* set led auto enable bit */ + fade_ctrl |= AB5500_FADE_MODE_MASK; + fade_ctrl |= AB5500_LED_FADE_ENABLE(led->id); + + /* set fade delay */ + fade_ctrl &= ~AB5500_FADE_DELAY_MASK; + fade_ctrl |= hvleds->fade_delay << AB5500_FADE_DELAY_SHIFT; + + /* set fade start manual */ + fade_ctrl |= AB5500_FADE_START_MASK; + + /* enble corresponding led */ + ret = ab5500_led_enable(hvleds, led->id); + if (ret < 0) + goto unlock_and_return; + + } + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + AB5500_LED_FADE_CTRL, fade_ctrl); + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + AB5500_LED_FADE_CTRL, ret); + goto unlock_and_return; + } + + hvleds->fade_auto = fade_auto; + + ret = len; + +unlock_and_return: + mutex_unlock(&hvleds->lock); + + return ret; +} + +static ssize_t ab5500_led_show_fade_auto(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ab5500_led *led = to_led(led_cdev); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + return sprintf(buf, "%d\n", hvleds->fade_auto); +} + +static ssize_t ab5500_led_store_fade_delay(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + unsigned long fade_delay; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ab5500_led *led = to_led(led_cdev); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + if (strict_strtoul(buf, 0, &fade_delay)) + return -EINVAL; + + if (fade_delay > AB5500_FADE_DELAY_TWOSEC) { + dev_err(hvleds->dev, "invalid mode\n"); + return -EINVAL; + } + + hvleds->fade_delay = fade_delay; + + return len; +} + +/* led class device attributes */ +static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, + ab5500_led_show_current, ab5500_led_store_current); +static DEVICE_ATTR(fade_auto, S_IRUGO | S_IWUGO, + ab5500_led_show_fade_auto, ab5500_led_store_fade_auto); +static DEVICE_ATTR(fade_delay, S_IRUGO | S_IWUGO, + NULL, ab5500_led_store_fade_delay); + +static int ab5500_led_init_registers(struct ab5500_hvleds *hvleds) +{ + int ret = 0; + unsigned int led_id; + + /* fade - manual : dur mid : pwm duty mid */ + if (!hvleds->hw_fade) { + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + AB5500_LED_REG_ENABLE, true); + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + AB5500_LED_REG_ENABLE, ret); + return ret; + } + } + + for (led_id = 0; led_id < AB5500_HVLEDS_MAX; led_id++) { + if (hvleds->leds[led_id].led_on == false) + continue; + + ret = ab5500_led_sinkctl_write( + hvleds, led_id, + hvleds->leds[led_id].max_current); + if (ret < 0) + return ret; + + if (hvleds->hw_fade) { + ret = ab5500_led_pwmfreq_write( + hvleds, led_id, + AB5500_LED_PWMFREQ_MAX / 2); + if (ret < 0) + return ret; + + /* fade high intensity */ + ret = ab5500_led_fade_write( + hvleds, led_id, true, + hvleds->leds[led_id].fade_hi); + if (ret < 0) + return ret; + + /* fade low intensity */ + ret = ab5500_led_fade_write( + hvleds, led_id, false, + hvleds->leds[led_id].fade_lo); + if (ret < 0) + return ret; + } + + /* init led off */ + ret |= ab5500_led_pwmduty_write( + hvleds, led_id, AB5500_LED_INTENSITY_OFF); + if (ret < 0) + return ret; + } + + return ret; +} + +static int ab5500_led_register_leds(struct device *dev, + struct ab5500_hvleds_platform_data *pdata, + struct ab5500_hvleds *hvleds) +{ + int i_led; + int ret = 0; + struct ab5500_led_conf *pled; + struct ab5500_led *led; + + hvleds->dev = dev; + hvleds->pdata = pdata; + + if (abx500_get_chip_id(dev) == AB5500_2_0) + hvleds->hw_fade = true; + else + hvleds->hw_fade = false; + + for (i_led = 0; i_led < AB5500_HVLEDS_MAX; i_led++) { + pled = &pdata->leds[i_led]; + led = &hvleds->leds[i_led]; + + INIT_WORK(&led->led_work, ab5500_led_work); + + led->id = pled->led_id; + led->max_current = pled->max_current; + led->led_on = pled->led_on; + led->led_cdev.name = pled->name; + led->led_cdev.brightness_set = ab5500_led_brightness_set; + + /* Provide interface only for enabled LEDs */ + if (led->led_on == false) + continue; + + if (hvleds->hw_fade) { + led->fade_hi = (pled->fade_hi & LED_FULL); + led->fade_hi *= AB5500_LED_INTENSITY_STEP; + led->fade_lo = (pled->fade_lo & LED_FULL); + led->fade_lo *= AB5500_LED_INTENSITY_STEP; + } + + ret = led_classdev_register(dev, &led->led_cdev); + if (ret < 0) { + dev_err(dev, "Register led class failed: %d\n", ret); + goto bailout1; + } + + ret = device_create_file(led->led_cdev.dev, + &dev_attr_led_current); + if (ret < 0) { + dev_err(dev, "sysfs device creation failed: %d\n", ret); + goto bailout2; + } + + if (hvleds->hw_fade) { + ret = device_create_file(led->led_cdev.dev, + &dev_attr_fade_auto); + if (ret < 0) { + dev_err(dev, "sysfs device " + "creation failed: %d\n", ret); + goto bailout3; + } + + ret = device_create_file(led->led_cdev.dev, + &dev_attr_fade_delay); + if (ret < 0) { + dev_err(dev, "sysfs device " + "creation failed: %d\n", ret); + goto bailout4; + } + } + } + + return ret; + for (; i_led >= 0; i_led--) { + if (hvleds->leds[i_led].led_on == false) + continue; + + if (hvleds->hw_fade) { + device_remove_file(hvleds->leds[i_led].led_cdev.dev, + &dev_attr_fade_delay); +bailout4: + device_remove_file(hvleds->leds[i_led].led_cdev.dev, + &dev_attr_fade_auto); + } +bailout3: + device_remove_file(hvleds->leds[i_led].led_cdev.dev, + &dev_attr_led_current); +bailout2: + led_classdev_unregister(&hvleds->leds[i_led].led_cdev); +bailout1: + cancel_work_sync(&hvleds->leds[i_led].led_work); + } + return ret; +} + +static int __devinit ab5500_hvleds_probe(struct platform_device *pdev) +{ + struct ab5500_hvleds_platform_data *pdata = pdev->dev.platform_data; + struct ab5500_hvleds *hvleds = NULL; + int ret = 0, i; + + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data required\n"); + ret = -ENODEV; + goto err_out; + } + + hvleds = kzalloc(sizeof(struct ab5500_hvleds), GFP_KERNEL); + if (hvleds == NULL) { + ret = -ENOMEM; + goto err_out; + } + + mutex_init(&hvleds->lock); + + /* init leds data and register led_classdev */ + ret = ab5500_led_register_leds(&pdev->dev, pdata, hvleds); + if (ret < 0) { + dev_err(&pdev->dev, "leds registration failed\n"); + goto err_out; + } + + /* init device registers and set initial led current */ + ret = ab5500_led_init_registers(hvleds); + if (ret < 0) { + dev_err(&pdev->dev, "reg init failed: %d\n", ret); + goto err_reg_init; + } + + if (hvleds->hw_fade) + dev_info(&pdev->dev, "v2 enabled\n"); + else + dev_info(&pdev->dev, "v1 enabled\n"); + + return ret; + +err_reg_init: + for (i = 0; i < AB5500_HVLEDS_MAX; i++) { + struct ab5500_led *led = &hvleds->leds[i]; + + if (led->led_on == false) + continue; + + device_remove_file(led->led_cdev.dev, &dev_attr_led_current); + if (hvleds->hw_fade) { + device_remove_file(led->led_cdev.dev, + &dev_attr_fade_auto); + device_remove_file(led->led_cdev.dev, + &dev_attr_fade_delay); + } + led_classdev_unregister(&led->led_cdev); + cancel_work_sync(&led->led_work); + } +err_out: + kfree(hvleds); + return ret; +} + +static int __devexit ab5500_hvleds_remove(struct platform_device *pdev) +{ + struct ab5500_hvleds *hvleds = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < AB5500_HVLEDS_MAX; i++) { + struct ab5500_led *led = &hvleds->leds[i]; + + if (led->led_on == false) + continue; + + device_remove_file(led->led_cdev.dev, &dev_attr_led_current); + if (hvleds->hw_fade) { + device_remove_file(led->led_cdev.dev, + &dev_attr_fade_auto); + device_remove_file(led->led_cdev.dev, + &dev_attr_fade_delay); + } + led_classdev_unregister(&led->led_cdev); + cancel_work_sync(&led->led_work); + } + kfree(hvleds); + return 0; +} + +static struct platform_driver ab5500_hvleds_driver = { + .driver = { + .name = AB5500LED_NAME, + .owner = THIS_MODULE, + }, + .probe = ab5500_hvleds_probe, + .remove = __devexit_p(ab5500_hvleds_remove), +}; + +static int __init ab5500_hvleds_module_init(void) +{ + return platform_driver_register(&ab5500_hvleds_driver); +} + +static void __exit ab5500_hvleds_module_exit(void) +{ + platform_driver_unregister(&ab5500_hvleds_driver); +} + +module_init(ab5500_hvleds_module_init); +module_exit(ab5500_hvleds_module_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Shreshtha Kumar SAHU <shreshthakumar.sahu@stericsson.com>"); +MODULE_DESCRIPTION("Driver for AB5500 HVLED"); + diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index 666daf77872..775cd67c604 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -27,6 +27,7 @@ struct led_pwm_data { struct led_classdev cdev; struct pwm_device *pwm; unsigned int active_low; + unsigned int lth_brightness; unsigned int period; }; @@ -42,7 +43,10 @@ static void led_pwm_set(struct led_classdev *led_cdev, pwm_config(led_dat->pwm, 0, period); pwm_disable(led_dat->pwm); } else { - pwm_config(led_dat->pwm, brightness * period / max, period); + brightness = led_dat->lth_brightness + (brightness * + (led_dat->period - led_dat->lth_brightness) / max); + pwm_config(led_dat->pwm, brightness, led_dat->period); + pwm_enable(led_dat->pwm); } } @@ -79,6 +83,8 @@ static int led_pwm_probe(struct platform_device *pdev) led_dat->cdev.default_trigger = cur_led->default_trigger; led_dat->active_low = cur_led->active_low; led_dat->period = cur_led->pwm_period_ns; + led_dat->lth_brightness = cur_led->lth_brightness * + (cur_led->pwm_period_ns / cur_led->max_brightness); led_dat->cdev.brightness_set = led_pwm_set; led_dat->cdev.brightness = LED_OFF; led_dat->cdev.max_brightness = cur_led->max_brightness; diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 21574bdf485..66226efb07a 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -304,6 +304,17 @@ config MFD_TC3589X additional drivers must be enabled in order to use the functionality of the device. +config MFD_TC35892 + bool "Support Toshiba TC35892" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + help + Support for the Toshiba TC35892 I/O Expander. + + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + config MFD_TMIO bool default n @@ -332,6 +343,27 @@ config MFD_TC6393XB help Support for Toshiba Mobile IO Controller TC6393XB +config AB5500_CORE + bool "ST-Ericsson AB5500 Mixed Signal Circuit core functions" + select MFD_CORE + depends on GENERIC_HARDIRQS && ABX500_CORE + help + Select this to enable the AB5500 Mixed Signal IC core + functionality. This connects to a AB5500 chip on the I2C bus via + the Power and Reset Management Unit (PRCMU). It exposes a number + of symbols needed for dependent devices to read and write + registers and subscribe to events from this multi-functional IC. + This is needed to use other features of the AB5500 such as + battery-backed RTC, charging control, Regulators, LEDs, vibrator, + system power and temperature, power management and ALSA sound. + +config AB5500_GPADC + bool "AB5500 GPADC driver" + depends on AB5500_CORE + default y + help + AB5500 GPADC driver used to convert battery/usb voltage. + config PMIC_DA903X bool "Dialog Semiconductor DA9030/DA9034 PMIC Support" depends on I2C=y @@ -589,7 +621,7 @@ config AB8500_CORE config AB8500_I2C_CORE bool "AB8500 register access via PRCMU I2C" - depends on AB8500_CORE && MFD_DB8500_PRCMU + depends on AB8500_CORE default y help This enables register access to the AB8500 chip via PRCMU I2C. @@ -597,6 +629,14 @@ config AB8500_I2C_CORE the I2C bus is connected to the Power Reset and Mangagement Unit, PRCMU. +config AB8500_DENC + bool "AB8500_DENC driver support(CVBS)" + depends on AB8500_CORE + help + Select this option to add driver support for analog TV out through + AB8500. + + config AB8500_DEBUG bool "Enable debug info via debugfs" depends on AB8500_CORE && DEBUG_FS @@ -607,10 +647,10 @@ config AB8500_DEBUG config AB8500_GPADC bool "AB8500 GPADC driver" - depends on AB8500_CORE && REGULATOR_AB8500 + depends on AB8500_CORE default y help - AB8500 GPADC driver used to convert Acc and battery/ac/usb voltage + AB8500 GPADC driver used to convert Acc and battery/ac/usb voltage. config AB3550_CORE bool "ST-Ericsson AB3550 Mixed Signal Circuit core functions" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index c58020303d1..db48396bc3b 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -2,6 +2,7 @@ # Makefile for multifunction miscellaneous devices # +obj-$(CONFIG_AB5500_CORE) += ab5500-core.o ab5500-power.o 88pm860x-objs := 88pm860x-core.o 88pm860x-i2c.o obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o obj-$(CONFIG_MFD_SM501) += sm501.o @@ -17,6 +18,7 @@ obj-$(CONFIG_MFD_TI_SSP) += ti-ssp.o obj-$(CONFIG_MFD_STMPE) += stmpe.o obj-$(CONFIG_MFD_TC3589X) += tc3589x.o +obj-$(CONFIG_MFD_TC35892) += tc35892.o obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o tmio_core.o obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o tmio_core.o @@ -82,11 +84,13 @@ obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o obj-$(CONFIG_AB3550_CORE) += ab3550-core.o obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o obj-$(CONFIG_AB8500_DEBUG) += ab8500-debugfs.o +obj-$(CONFIG_AB8500_DENC) += ab8500-denc.o obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o # ab8500-i2c need to come after db8500-prcmu (which provides the channel) obj-$(CONFIG_AB8500_I2C_CORE) += ab8500-i2c.o obj-$(CONFIG_MFD_DB5500_PRCMU) += db5500-prcmu.o +obj-$(CONFIG_AB5500_GPADC) += ab5500-gpadc.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o obj-$(CONFIG_LPC_SCH) += lpc_sch.o diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c new file mode 100755 index 00000000000..0d39a0caff0 --- /dev/null +++ b/drivers/mfd/ab5500-core.c @@ -0,0 +1,2677 @@ +/* + * Copyright (C) 2007-2011 ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * Low-level core for exclusive access to the AB5500 IC on the I2C bus + * and some basic chip-configuration. + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com> + * Author: Mattias Nilsson <mattias.i.nilsson@stericsson.com> + * Author: Mattias Wallin <mattias.wallin@stericsson.com> + * Author: Rickard Andersson <rickard.andersson@stericsson.com> + * Author: Karl Komierowski <karl.komierowski@stericsson.com> + * Author: Bibek Basu <bibek.basu@stericsson.com> + */ + +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/random.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/list.h> +#include <linux/bitops.h> +#include <linux/spinlock.h> +#include <linux/mfd/core.h> +#include <linux/version.h> +#include <linux/io.h> +#include <linux/mfd/dbx500-prcmu.h> + +#define AB5500_NAME_STRING "ab5500" +#define AB5500_ID_FORMAT_STRING "AB5500 %s" +#define AB5500_NUM_EVENT_V1_REG 23 +#define AB5500_IT_LATCH0_REG 0x40 +#define AB5500_IT_MASK0_REG 0x60 +/* These are the only registers inside AB5500 used in this main file */ + +/* Read/write operation values. */ +#define AB5500_PERM_RD (0x01) +#define AB5500_PERM_WR (0x02) + +/* Read/write permissions. */ +#define AB5500_PERM_RO (AB5500_PERM_RD) +#define AB5500_PERM_RW (AB5500_PERM_RD | AB5500_PERM_WR) + +#define AB5500_MASK_BASE (0x60) +#define AB5500_MASK_END (0x79) +#define AB5500_CHIP_ID (0x20) +#define AB5500_INTERRUPTS 0x01FFFFFF + +/* Turn On Status Event */ +#define RTC_ALARM 0x80 +#define POW_KEY_2_ON 0x20 +#define POW_KEY_1_ON 0x08 +#define POR_ON_VBAT 0x10 +#define VBUS_DET 0x20 +#define VBUS_CH_DROP_R 0x08 +#define USB_CH_DET_DONE 0x02 + +/* Global Variables */ +u8 turn_on_stat = 0x00; + +/** + * struct ab5500_bank + * @slave_addr: I2C slave_addr found in AB5500 specification + * @name: Documentation name of the bank. For reference + */ +struct ab5500_bank { + u8 slave_addr; + const char *name; +}; + +static const struct ab5500_bank bankinfo[AB5500_NUM_BANKS] = { + [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { + AB5500_ADDR_VIT_IO_I2C_CLK_TST_OTP, "VIT_IO_I2C_CLK_TST_OTP"}, + [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = { + AB5500_ADDR_VDDDIG_IO_I2C_CLK_TST, "VDDDIG_IO_I2C_CLK_TST"}, + [AB5500_BANK_VDENC] = {AB5500_ADDR_VDENC, "VDENC"}, + [AB5500_BANK_SIM_USBSIM] = {AB5500_ADDR_SIM_USBSIM, "SIM_USBSIM"}, + [AB5500_BANK_LED] = {AB5500_ADDR_LED, "LED"}, + [AB5500_BANK_ADC] = {AB5500_ADDR_ADC, "ADC"}, + [AB5500_BANK_RTC] = {AB5500_ADDR_RTC, "RTC"}, + [AB5500_BANK_STARTUP] = {AB5500_ADDR_STARTUP, "STARTUP"}, + [AB5500_BANK_DBI_ECI] = {AB5500_ADDR_DBI_ECI, "DBI-ECI"}, + [AB5500_BANK_CHG] = {AB5500_ADDR_CHG, "CHG"}, + [AB5500_BANK_FG_BATTCOM_ACC] = { + AB5500_ADDR_FG_BATTCOM_ACC, "FG_BATCOM_ACC"}, + [AB5500_BANK_USB] = {AB5500_ADDR_USB, "USB"}, + [AB5500_BANK_IT] = {AB5500_ADDR_IT, "IT"}, + [AB5500_BANK_VIBRA] = {AB5500_ADDR_VIBRA, "VIBRA"}, + [AB5500_BANK_AUDIO_HEADSETUSB] = { + AB5500_ADDR_AUDIO_HEADSETUSB, "AUDIO_HEADSETUSB"}, +}; + +#define AB5500_IRQ(bank, bit) ((bank) * 8 + (bit)) + +/* I appologize for the resource names beeing a mix of upper case + * and lower case but I want them to be exact as the documentation */ +static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { + [AB5500_DEVID_LEDS] = { + .name = "ab5500-leds", + }, + [AB5500_DEVID_POWER] = { + .name = "ab5500-power", + }, + [AB5500_DEVID_REGULATORS] = { + .name = "ab5500-regulator", + }, + [AB5500_DEVID_SIM] = { + .name = "ab5500-sim", + .num_resources = 1, + .resources = (struct resource[]) { + { + .name = "SIMOFF", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 0), /*rising*/ + .end = AB5500_IRQ(2, 1), /*falling*/ + }, + }, + }, + [AB5500_DEVID_RTC] = { + .name = "ab5500-rtc", + .num_resources = 1, + .resources = (struct resource[]) { + { + .name = "RTC_Alarm", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(1, 7), + .end = AB5500_IRQ(1, 7), + } + }, + }, + [AB5500_DEVID_CHARGER] = { + .name = "ab5500-charger", + .num_resources = 29, + .resources = (struct resource[]) { + { + .name = "VBAT_INSERT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 4), + .end = AB5500_IRQ(2, 4), + }, + { + .name = "TEMP_ASIC_ALARM", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 2), + .end = AB5500_IRQ(2, 2), + }, + { + .name = "BATT_REMOVAL", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 6), + .end = AB5500_IRQ(7, 6), + }, + { + .name = "BATT_ATTACH", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 5), + .end = AB5500_IRQ(7, 5), + }, + { + .name = "CGSTATE_10_PCVBUS_CHG", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 7), + .end = AB5500_IRQ(8, 7), + }, + { + .name = "VBUS_FALLING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 6), + .end = AB5500_IRQ(8, 6), + }, + { + .name = "VBUS_RISING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 5), + .end = AB5500_IRQ(8, 5), + }, + { + .name = "UART_RDY_TX", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 2), + .end = AB5500_IRQ(8, 2), + }, + { + .name = "UART_RDY_RX", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 1), + .end = AB5500_IRQ(8, 1), + }, + { + .name = "UART_OVERRUN", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 0), + .end = AB5500_IRQ(8, 0), + }, + { + .name = "VBUS_IMEAS_MAX_CHANGE_RISING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 7), + .end = AB5500_IRQ(9, 7), + }, + { + .name = "USB_SUSPEND", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 3), + .end = AB5500_IRQ(9, 3), + }, + { + .name = "USB_CHAR_DET_DONE", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 1), + .end = AB5500_IRQ(9, 1), + }, + { + .name = "VBUS_IMEAS_MAX_CHANGE_FALLING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(10, 0), + .end = AB5500_IRQ(10, 0), + }, + { + .name = "OVV", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 5), + .end = AB5500_IRQ(14, 5), + }, + { + .name = "USB_CH_TH_PROTECTION", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 4), + .end = AB5500_IRQ(15, 4), + }, + { + .name = "USB_CH_NOT_OK", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 3), + .end = AB5500_IRQ(15, 3), + }, + { + .name = "CHAR_TEMP_WINDOW_OK_RISING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 7), + .end = AB5500_IRQ(17, 7), + }, + { + .name = "CHARGING_STOPPED_BY_TEMP", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 6), + .end = AB5500_IRQ(18, 6), + }, + { + .name = "VBUS_DROP_FALLING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 4), + .end = AB5500_IRQ(18, 4), + }, + { + .name = "VBUS_DROP_RISING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 3), + .end = AB5500_IRQ(18, 3), + }, + { + .name = "CHAR_TEMP_WINDOW_OK_FALLING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 0), + .end = AB5500_IRQ(18, 0), + }, + { + .name = "CHG_STATE_13_COMP_VBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 3), + .end = AB5500_IRQ(21, 3), + }, + { + .name = "CHG_STATE_12_COMP_VBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 2), + .end = AB5500_IRQ(21, 2), + }, + { + .name = "CHG_STATE_11_SAFE_MODE_VBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 1), + .end = AB5500_IRQ(21, 1), + }, + { + .name = "USB_LINK_UPDATE", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(22, 1), + .end = AB5500_IRQ(22, 1), + }, + { + .name = "CHG_SW_TIMER_OUT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(23, 7), + .end = AB5500_IRQ(23, 7), + }, + { + .name = "CHG_HW_TIMER_OUT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(23, 6), + .end = AB5500_IRQ(23, 6), + }, + }, + }, + [AB5500_DEVID_CHARGALG] = { + .name = "abx500-chargalg", + }, + [AB5500_DEVID_BTEMP] = { + .name = "ab5500-btemp", + .num_resources = 2, + .resources = (struct resource[]) { + { + .name = "BATT_ATTACH", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 5), + .end = AB5500_IRQ(7, 5), + }, + { + .name = "BATT_REMOVAL", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 6), + .end = AB5500_IRQ(7, 6), + }, + }, + }, + [AB5500_DEVID_ADC] = { + .name = "ab5500-adc", + .num_resources = 10, + .resources = (struct resource[]) { + { + .name = "TRIGGER-0", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 0), + .end = AB5500_IRQ(0, 0), + }, + { + .name = "TRIGGER-1", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 1), + .end = AB5500_IRQ(0, 1), + }, + { + .name = "TRIGGER-2", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 2), + .end = AB5500_IRQ(0, 2), + }, + { + .name = "TRIGGER-3", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 3), + .end = AB5500_IRQ(0, 3), + }, + { + .name = "TRIGGER-4", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 4), + .end = AB5500_IRQ(0, 4), + }, + { + .name = "TRIGGER-5", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 5), + .end = AB5500_IRQ(0, 5), + }, + { + .name = "TRIGGER-6", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 6), + .end = AB5500_IRQ(0, 6), + }, + { + .name = "TRIGGER-7", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 7), + .end = AB5500_IRQ(0, 7), + }, + { + .name = "TRIGGER-VBAT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(1, 0), + .end = AB5500_IRQ(1, 0), + }, + { + .name = "TRIGGER-VBAT-TXON", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(1, 1), + .end = AB5500_IRQ(1, 1), + }, + }, + }, + [AB5500_DEVID_FG] = { + .name = "ab5500-fg", + .num_resources = 6, + .resources = (struct resource[]) { + { + .name = "Batt_attach", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 5), + .end = AB5500_IRQ(7, 5), + }, + { + .name = "Batt_removal", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 6), + .end = AB5500_IRQ(7, 6), + }, + { + .name = "UART_framing", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 7), + .end = AB5500_IRQ(7, 7), + }, + { + .name = "UART_overrun", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 0), + .end = AB5500_IRQ(8, 0), + }, + { + .name = "UART_Rdy_RX", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 1), + .end = AB5500_IRQ(8, 1), + }, + { + .name = "UART_Rdy_TX", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 2), + .end = AB5500_IRQ(8, 2), + }, + }, + }, + [AB5500_DEVID_VIBRATOR] = { + .name = "ab5500-vibrator", + }, + [AB5500_DEVID_CODEC] = { + .name = "ab5500-codec", + .num_resources = 3, + .resources = (struct resource[]) { + { + .name = "audio_spkr1_ovc", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 5), + .end = AB5500_IRQ(9, 5), + }, + { + .name = "audio_plllocked", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 6), + .end = AB5500_IRQ(9, 6), + }, + { + .name = "audio_spkr2_ovc", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 4), + .end = AB5500_IRQ(17, 4), + }, + }, + }, + [AB5500_DEVID_USB] = { + .name = "ab5500-usb", + .num_resources = 36, + .resources = (struct resource[]) { + { + .name = "Link_Update", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(22, 1), + .end = AB5500_IRQ(22, 1), + }, + { + .name = "DCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 3), + .end = AB5500_IRQ(8, 4), + }, + { + .name = "VBUS_R", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 5), + .end = AB5500_IRQ(8, 5), + }, + { + .name = "VBUS_F", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 6), + .end = AB5500_IRQ(8, 6), + }, + { + .name = "CHGstate_10_PCVBUSchg", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 7), + .end = AB5500_IRQ(8, 7), + }, + { + .name = "DCIOreverse_ovc", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 0), + .end = AB5500_IRQ(9, 0), + }, + { + .name = "USBCharDetDone", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 1), + .end = AB5500_IRQ(9, 1), + }, + { + .name = "DCIO_no_limit", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 2), + .end = AB5500_IRQ(9, 2), + }, + { + .name = "USB_suspend", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 3), + .end = AB5500_IRQ(9, 3), + }, + { + .name = "DCIOreverse_fwdcurrent", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 4), + .end = AB5500_IRQ(9, 4), + }, + { + .name = "Vbus_Imeasmax_change", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 7), + .end = AB5500_IRQ(9, 7), + }, + { + .name = "OVV", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 5), + .end = AB5500_IRQ(14, 5), + }, + { + .name = "USBcharging_NOTok", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 3), + .end = AB5500_IRQ(15, 3), + }, + { + .name = "usb_adp_sensoroff", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 6), + .end = AB5500_IRQ(15, 6), + }, + { + .name = "usb_adp_probeplug", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 7), + .end = AB5500_IRQ(15, 7), + }, + { + .name = "usb_adp_sinkerror", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 0), + .end = AB5500_IRQ(16, 6), + }, + { + .name = "usb_adp_sourceerror", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 1), + .end = AB5500_IRQ(16, 1), + }, + { + .name = "usb_idgnd_r", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 2), + .end = AB5500_IRQ(16, 2), + }, + { + .name = "usb_idgnd_f", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 3), + .end = AB5500_IRQ(16, 3), + }, + { + .name = "usb_iddetR1", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 4), + .end = AB5500_IRQ(16, 5), + }, + { + .name = "usb_iddetR2", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 6), + .end = AB5500_IRQ(16, 7), + }, + { + .name = "usb_iddetR3", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 0), + .end = AB5500_IRQ(17, 1), + }, + { + .name = "usb_iddetR4", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 2), + .end = AB5500_IRQ(17, 3), + }, + { + .name = "CharTempWindowOk", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 7), + .end = AB5500_IRQ(18, 0), + }, + { + .name = "USB_SprDetect", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 1), + .end = AB5500_IRQ(18, 1), + }, + { + .name = "usb_adp_probe_unplug", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 2), + .end = AB5500_IRQ(18, 2), + }, + { + .name = "VBUSChDrop", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 3), + .end = AB5500_IRQ(18, 4), + }, + { + .name = "dcio_char_rec_done", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 5), + .end = AB5500_IRQ(18, 5), + }, + { + .name = "Charging_stopped_by_temp", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 6), + .end = AB5500_IRQ(18, 6), + }, + { + .name = "CHGstate_11_SafeModeVBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 1), + .end = AB5500_IRQ(21, 1), + }, + { + .name = "CHGstate_12_comletedVBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 2), + .end = AB5500_IRQ(21, 2), + }, + { + .name = "CHGstate_13_completedVBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 3), + .end = AB5500_IRQ(21, 3), + }, + { + .name = "CHGstate_14_FullChgDCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 4), + .end = AB5500_IRQ(21, 4), + }, + { + .name = "CHGstate_15_SafeModeDCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 5), + .end = AB5500_IRQ(21, 5), + }, + { + .name = "CHGstate_16_OFFsuspendDCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 6), + .end = AB5500_IRQ(21, 6), + }, + { + .name = "CHGstate_17_completedDCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 7), + .end = AB5500_IRQ(21, 7), + }, + }, + }, + [AB5500_DEVID_OTP] = { + .name = "ab5500-otp", + }, + [AB5500_DEVID_VIDEO] = { + .name = "ab5500-video", + .num_resources = 2, + .resources = (struct resource[]) { + { + .name = "plugTVdet", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(13, 7), + .end = AB5500_IRQ(13, 7), + }, + { + .name = "plugTVdet_removal", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(23, 2), + .end = AB5500_IRQ(23, 2), + }, + + }, + }, + [AB5500_DEVID_DBIECI] = { + .name = "ab5500-dbieci", + .num_resources = 10, + .resources = (struct resource[]) { + { + .name = "COLL", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 0), + .end = AB5500_IRQ(14, 0), + }, + { + .name = "RESERR", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 1), + .end = AB5500_IRQ(14, 1), + }, + { + .name = "FRAERR", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 2), + .end = AB5500_IRQ(14, 2), + }, + { + .name = "COMERR", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 3), + .end = AB5500_IRQ(14, 3), + }, + { + .name = "BSI_indicator", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 4), + .end = AB5500_IRQ(14, 4), + }, + { + .name = "SPDSET", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 6), + .end = AB5500_IRQ(14, 6), + }, + { + .name = "DSENT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 7), + .end = AB5500_IRQ(14, 7), + }, + { + .name = "DREC", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 0), + .end = AB5500_IRQ(15, 0), + }, + { + .name = "ACCINT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 1), + .end = AB5500_IRQ(15, 1), + }, + { + .name = "NOPINT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 2), + .end = AB5500_IRQ(15, 2), + }, + }, + }, + [AB5500_DEVID_ONSWA] = { + .name = "ab5500-onswa", + .num_resources = 2, + .resources = (struct resource[]) { + { + .name = "ONSWAn_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(1, 3), + .end = AB5500_IRQ(1, 3), + }, + { + .name = "ONSWAn_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(1, 4), + .end = AB5500_IRQ(1, 4), + }, + }, + }, + [AB5500_DEVID_TEMPMON] = { + .name = "abx500-temp", + .id = AB5500_DEVID_TEMPMON, + .num_resources = 1, + .resources = (struct resource[]) { + { + .name = "ABX500_TEMP_WARM", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 2), + .end = AB5500_IRQ(2, 2), + }, + }, + }, + [AB5500_DEVID_ACCDET] = { + .name = "ab5500-acc-det", + .id = AB5500_DEVID_ACCDET, + .num_resources = 8, + .resources = (struct resource[]) { + { + .name = "acc_detedt22db_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 7), + .end = AB5500_IRQ(2, 7), + }, + { + .name = "acc_detedt21db_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 6), + .end = AB5500_IRQ(2, 6), + }, + { + .name = "acc_detedt21db_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 5), + .end = AB5500_IRQ(2, 5), + }, + { + .name = "acc_detedt3db_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 4), + .end = AB5500_IRQ(3, 4), + }, + { + .name = "acc_detedt3db_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 3), + .end = AB5500_IRQ(3, 3), + }, + { + .name = "acc_detedt1db_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 2), + .end = AB5500_IRQ(3, 2), + }, + { + .name = "acc_detedt1db_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 1), + .end = AB5500_IRQ(3, 1), + }, + { + .name = "acc_detedt22db_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 0), + .end = AB5500_IRQ(3, 0), + }, + }, + }, +}; + +static ssize_t show_chip_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ab5500 *ab5500; + + ab5500 = dev_get_drvdata(dev); + return sprintf(buf, "%#x\n", ab5500 ? ab5500->chip_id : -EINVAL); +} + +static ssize_t show_turn_on_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%#x\n", turn_on_stat); +} + +static DEVICE_ATTR(chip_id, S_IRUGO, show_chip_id, NULL); +static DEVICE_ATTR(turn_on_status, S_IRUGO, show_turn_on_status, NULL); + +static struct attribute *ab5500_sysfs_entries[] = { + &dev_attr_chip_id.attr, + &dev_attr_turn_on_status.attr, + NULL, +}; + +static struct attribute_group ab5500_attr_group = { + .attrs = ab5500_sysfs_entries, +}; + +/* + * Functionality for getting/setting register values. + */ +static int get_register_interruptible(struct ab5500 *ab, u8 bank, u8 reg, + u8 *value) +{ + int err; + + if (bank >= AB5500_NUM_BANKS) + return -EINVAL; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, reg, value, 1); + + mutex_unlock(&ab->access_mutex); + return err; +} + +static int get_register_page_interruptible(struct ab5500 *ab, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + int err; + + if (bank >= AB5500_NUM_BANKS) + return -EINVAL; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + + while (numregs) { + /* The hardware limit for get page is 4 */ + u8 curnum = min_t(u8, numregs, 4u); + + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, + first_reg, regvals, curnum); + if (err) + goto out; + + numregs -= curnum; + first_reg += curnum; + regvals += curnum; + } + +out: + mutex_unlock(&ab->access_mutex); + return err; +} + +static int mask_and_set_register_interruptible(struct ab5500 *ab, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + int err = 0; + + if (bank >= AB5500_NUM_BANKS) + return -EINVAL; + + if (bitmask) { + u8 buf; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + + if (bitmask == 0xFF) /* No need to read in this case. */ + buf = bitvalues; + else { /* Read and modify the register value. */ + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, + reg, &buf, 1); + if (err) + return err; + + buf = ((~bitmask & buf) | (bitmask & bitvalues)); + } + /* Write the new value. */ + err = db5500_prcmu_abb_write(bankinfo[bank].slave_addr, reg, + &buf, 1); + + mutex_unlock(&ab->access_mutex); + } + return err; +} + +static int +set_register_interruptible(struct ab5500 *ab, u8 bank, u8 reg, u8 value) +{ + return mask_and_set_register_interruptible(ab, bank, reg, 0xff, value); +} + +/* + * The exported register access functionality. + */ +static int ab5500_get_chip_id(struct device *dev) +{ + struct ab5500 *ab = dev_get_drvdata(dev->parent); + + return (int)ab->chip_id; +} + +static int ab5500_mask_and_set_register_interruptible(struct device *dev, + u8 bank, u8 reg, u8 bitmask, u8 bitvalues) +{ + struct ab5500 *ab; + + ab = dev_get_drvdata(dev->parent); + return mask_and_set_register_interruptible(ab, bank, reg, + bitmask, bitvalues); +} + +static int ab5500_set_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 value) +{ + return ab5500_mask_and_set_register_interruptible(dev, bank, reg, 0xFF, + value); +} + +static int ab5500_get_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 *value) +{ + struct ab5500 *ab; + + ab = dev_get_drvdata(dev->parent); + return get_register_interruptible(ab, bank, reg, value); +} + +static int ab5500_get_register_page_interruptible(struct device *dev, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + struct ab5500 *ab; + + ab = dev_get_drvdata(dev->parent); + return get_register_page_interruptible(ab, bank, first_reg, regvals, + numregs); +} + +static int +ab5500_event_registers_startup_state_get(struct device *dev, u8 *event) +{ + struct ab5500 *ab; + + ab = dev_get_drvdata(dev->parent); + if (!ab->startup_events_read) + return -EAGAIN; /* Try again later */ + + memcpy(event, ab->startup_events, ab->num_event_reg); + return 0; +} + +static int ab5500_startup_irq_enabled(struct device *dev, unsigned int irq) +{ + struct ab5500 *ab; + bool val; + + ab = irq_get_chip_data(irq); + irq -= ab->irq_base; + val = ((ab->startup_events[irq / 8] & BIT(irq % 8)) != 0); + + return val; +} + +static struct abx500_ops ab5500_ops = { + .get_chip_id = ab5500_get_chip_id, + .get_register = ab5500_get_register_interruptible, + .set_register = ab5500_set_register_interruptible, + .get_register_page = ab5500_get_register_page_interruptible, + .set_register_page = NULL, + .mask_and_set_register = ab5500_mask_and_set_register_interruptible, + .event_registers_startup_state_get = + ab5500_event_registers_startup_state_get, + .startup_irq_enabled = ab5500_startup_irq_enabled, +}; + +static irqreturn_t ab5500_irq(int irq, void *data) +{ + struct ab5500 *ab = data; + u8 i; + u8 *pvalue; + u8 value; + + prcmu_get_abb_event_buffer((void **)&pvalue); + if (unlikely(pvalue == NULL)) { + dev_err(ab->dev, "PRCMU not enabled!!!\n"); + goto error_irq; + } + for (i = 0; i < ab->num_event_reg; i++) { + value = readb(pvalue); + if (value == 0) { + pvalue++; + continue; + } + + do { + int bit = __ffs(value); + int line = i * 8 + bit; + + handle_nested_irq(ab->irq_base + line); + value &= ~(1 << bit); + } while (value); + pvalue++; + } + + return IRQ_HANDLED; +error_irq: + return IRQ_NONE; +} + +#ifdef CONFIG_DEBUG_FS +/** + * struct ab5500_reg_range + * @first: the first address of the range + * @last: the last address of the range + */ +struct ab5500_reg_range { + u8 first; + u8 last; +}; + +/** + * struct ab5500_i2c_ranges + * @count: the number of ranges in the list + * @range: the list of register ranges + */ +struct ab5500_i2c_ranges { + u8 nranges; + u8 bankid; + const struct ab5500_reg_range *range; +}; + +static struct ab5500_i2c_ranges ab5500v1_reg_ranges[AB5500_NUM_BANKS] = { + [AB5500_BANK_LED] = { + .bankid = AB5500_BANK_LED, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0C, + }, + }, + }, + [AB5500_BANK_ADC] = { + .bankid = AB5500_BANK_ADC, + .nranges = 6, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x1F, + .last = 0x21, + }, + { + .first = 0x22, + .last = 0x24, + }, + { + .first = 0x26, + .last = 0x2D, + }, + { + .first = 0x2F, + .last = 0x34, + }, + { + .first = 0x37, + .last = 0x57, + }, + { + .first = 0x58, + .last = 0x58, + }, + }, + }, + [AB5500_BANK_RTC] = { + .bankid = AB5500_BANK_RTC, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x06, + .last = 0x0C, + }, + }, + }, + [AB5500_BANK_STARTUP] = { + .bankid = AB5500_BANK_STARTUP, + .nranges = 12, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + }, + { + .first = 0x1F, + .last = 0x1F, + }, + { + .first = 0x2E, + .last = 0x2E, + }, + { + .first = 0x2F, + .last = 0x30, + }, + { + .first = 0x50, + .last = 0x51, + }, + { + .first = 0x60, + .last = 0x61, + }, + { + .first = 0x66, + .last = 0x8A, + }, + { + .first = 0x8C, + .last = 0x96, + }, + { + .first = 0xAA, + .last = 0xB4, + }, + { + .first = 0xB7, + .last = 0xBF, + }, + { + .first = 0xC1, + .last = 0xCA, + }, + { + .first = 0xD3, + .last = 0xE0, + }, + }, + }, + [AB5500_BANK_DBI_ECI] = { + .bankid = AB5500_BANK_DBI_ECI, + .nranges = 3, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x07, + }, + { + .first = 0x10, + .last = 0x10, + }, + { + .first = 0x13, + .last = 0x13, + }, + }, + }, + [AB5500_BANK_CHG] = { + .bankid = AB5500_BANK_CHG, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x11, + .last = 0x11, + }, + { + .first = 0x12, + .last = 0x1B, + }, + }, + }, + [AB5500_BANK_FG_BATTCOM_ACC] = { + .bankid = AB5500_BANK_FG_BATTCOM_ACC, + .nranges = 5, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0B, + }, + { + .first = 0x0C, + .last = 0x10, + }, + { + .first = 0x1A, + .last = 0x1D, + }, + { + .first = 0x20, + .last = 0x21, + }, + { + .first = 0x23, + .last = 0x24, + }, + + }, + }, + [AB5500_BANK_USB] = { + .bankid = AB5500_BANK_USB, + .nranges = 13, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x01, + }, + { + .first = 0x80, + .last = 0x80, + }, + { + .first = 0x81, + .last = 0x83, + }, + { + .first = 0x87, + .last = 0x8A, + }, + { + .first = 0x8B, + .last = 0x8B, + }, + { + .first = 0x91, + .last = 0x92, + }, + { + .first = 0x93, + .last = 0x93, + }, + { + .first = 0x94, + .last = 0x94, + }, + { + .first = 0xA8, + .last = 0xB0, + }, + { + .first = 0xB2, + .last = 0xB2, + }, + { + .first = 0xB4, + .last = 0xBC, + }, + { + .first = 0xBF, + .last = 0xBF, + }, + { + .first = 0xC1, + .last = 0xC5, + }, + }, + }, + [AB5500_BANK_IT] = { + .bankid = AB5500_BANK_IT, + .nranges = 4, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x02, + }, + { + .first = 0x20, + .last = 0x36, + }, + { + .first = 0x40, + .last = 0x56, + }, + { + .first = 0x60, + .last = 0x76, + }, + }, + }, + [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = { + .bankid = AB5500_BANK_VDDDIG_IO_I2C_CLK_TST, + .nranges = 7, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x02, + .last = 0x02, + }, + { + .first = 0x12, + .last = 0x12, + }, + { + .first = 0x30, + .last = 0x34, + }, + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x50, + .last = 0x54, + }, + { + .first = 0x60, + .last = 0x64, + }, + { + .first = 0x70, + .last = 0x74, + }, + }, + }, + [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { + .bankid = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + .nranges = 14, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x01, + }, + { + .first = 0x02, + .last = 0x02, + }, + { + .first = 0x0D, + .last = 0x0D, + }, + { + .first = 0x0E, + .last = 0x0E, + }, + { + .first = 0x1C, + .last = 0x1C, + }, + { + .first = 0x1E, + .last = 0x1E, + }, + { + .first = 0x20, + .last = 0x21, + }, + { + .first = 0x28, + .last = 0x28, + }, + { + .first = 0x30, + .last = 0x33, + }, + { + .first = 0x40, + .last = 0x43, + }, + { + .first = 0x50, + .last = 0x53, + }, + { + .first = 0x60, + .last = 0x63, + }, + { + .first = 0x70, + .last = 0x73, + }, + { + .first = 0xB1, + .last = 0xB1, + }, + }, + }, + [AB5500_BANK_VIBRA] = { + .bankid = AB5500_BANK_VIBRA, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x10, + .last = 0x13, + }, + }, + }, + [AB5500_BANK_AUDIO_HEADSETUSB] = { + .bankid = AB5500_BANK_AUDIO_HEADSETUSB, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x47, + }, + }, + }, + [AB5500_BANK_SIM_USBSIM] = { + .bankid = AB5500_BANK_SIM_USBSIM, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x13, + .last = 0x19, + }, + { + .first = 0x20, + .last = 0x20, + }, + }, + }, + [AB5500_BANK_VDENC] = { + .bankid = AB5500_BANK_VDENC, + .nranges = 12, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x08, + }, + { + .first = 0x09, + .last = 0x09, + }, + { + .first = 0x0A, + .last = 0x12, + }, + { + .first = 0x15, + .last = 0x19, + }, + { + .first = 0x1B, + .last = 0x21, + }, + { + .first = 0x27, + .last = 0x2C, + }, + { + .first = 0x41, + .last = 0x41, + }, + { + .first = 0x45, + .last = 0x5B, + }, + { + .first = 0x5D, + .last = 0x5D, + }, + { + .first = 0x69, + .last = 0x69, + }, + { + .first = 0x6C, + .last = 0x6D, + }, + { + .first = 0x80, + .last = 0x81, + }, + }, + }, +}; + +static struct ab5500_i2c_ranges ab5500v2_reg_ranges[AB5500_NUM_BANKS] = { + [AB5500_BANK_LED] = { + .bankid = AB5500_BANK_LED, + .nranges = 3, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x02, + }, + { + .first = 0x04, + .last = 0x0D, + }, + { + .first = 0x10, + .last = 0x1B, + }, + }, + }, + [AB5500_BANK_ADC] = { + .bankid = AB5500_BANK_ADC, + .nranges = 4, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x1F, + .last = 0x24, + }, + { + .first = 0x26, + .last = 0x35, + }, + { + .first = 0x37, + .last = 0x57, + }, + { + .first = 0xA0, + .last = 0xA5, + }, + }, + }, + [AB5500_BANK_RTC] = { + .bankid = AB5500_BANK_RTC, + .nranges = 3, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x06, + .last = 0x0B, + }, + { + .first = 0x20, + .last = 0x21, + }, + }, + }, + [AB5500_BANK_STARTUP] = { + .bankid = AB5500_BANK_STARTUP, + .nranges = 14, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + }, + { + .first = 0x1F, + .last = 0x1F, + }, + { + .first = 0x2E, + .last = 0x31, + }, + { + .first = 0x50, + .last = 0x51, + }, + { + .first = 0x60, + .last = 0x61, + }, + { + .first = 0x66, + .last = 0x8A, + }, + { + .first = 0x8C, + .last = 0x96, + }, + { + .first = 0xAA, + .last = 0xB5, + }, + { + .first = 0xB7, + .last = 0xBF, + }, + { + .first = 0xC1, + .last = 0xCA, + }, + { + .first = 0xD3, + .last = 0xE0, + }, + { + .first = 0xEA, + .last = 0xEA, + }, + { + .first = 0xF0, + .last = 0xF0, + }, + { + .first = 0xF6, + .last = 0xF6, + }, + }, + }, + [AB5500_BANK_DBI_ECI] = { + .bankid = AB5500_BANK_DBI_ECI, + .nranges = 3, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x07, + }, + { + .first = 0x10, + .last = 0x10, + }, + { + .first = 0x13, + .last = 0x13, + }, + }, + }, + [AB5500_BANK_CHG] = { + .bankid = AB5500_BANK_CHG, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x11, + .last = 0x1D, + }, + }, + }, + [AB5500_BANK_FG_BATTCOM_ACC] = { + .bankid = AB5500_BANK_FG_BATTCOM_ACC, + .nranges = 5, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x10, + }, + { + .first = 0x1A, + .last = 0x1D, + }, + { + .first = 0x20, + .last = 0x20, + }, + { + .first = 0x24, + .last = 0x24, + }, + { + .first = 0x30, + .last = 0x37, + }, + + }, + }, + [AB5500_BANK_USB] = { + .bankid = AB5500_BANK_USB, + .nranges = 5, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x01, + }, + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x87, + .last = 0x8B, + }, + { + .first = 0x91, + .last = 0x94, + }, + { + .first = 0x98, + .last = 0x9A, + }, + }, + }, + [AB5500_BANK_IT] = { + .bankid = AB5500_BANK_IT, + .nranges = 4, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x03, + }, + { + .first = 0x20, + .last = 0x38, + }, + { + .first = 0x40, + .last = 0x58, + }, + { + .first = 0x60, + .last = 0x78, + }, + }, + }, + [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = { + .bankid = AB5500_BANK_VDDDIG_IO_I2C_CLK_TST, + .nranges = 7, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x02, + .last = 0x02, + }, + { + .first = 0x12, + .last = 0x12, + }, + { + .first = 0x30, + .last = 0x34, + }, + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x50, + .last = 0x54, + }, + { + .first = 0x60, + .last = 0x64, + }, + { + .first = 0x70, + .last = 0x74, + }, + }, + }, + [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { + .bankid = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + .nranges = 17, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x02, + }, + { + .first = 0x0D, + .last = 0x0F, + }, + { + .first = 0x19, + .last = 0x1C, + }, + { + .first = 0x1E, + .last = 0x1E, + }, + { + .first = 0x20, + .last = 0x20, + }, + { + .first = 0x28, + .last = 0x28, + }, + { + .first = 0x30, + .last = 0x33, + }, + { + .first = 0x35, + .last = 0x35, + }, + { + .first = 0x40, + .last = 0x43, + }, + { + .first = 0x45, + .last = 0x45, + }, + { + .first = 0x50, + .last = 0x53, + }, + { + .first = 0x55, + .last = 0x55, + }, + { + .first = 0x60, + .last = 0x63, + }, + { + .first = 0x65, + .last = 0x65, + }, + { + .first = 0x70, + .last = 0x73, + }, + { + .first = 0x75, + .last = 0x75, + }, + { + .first = 0xB1, + .last = 0xB1, + }, + }, + }, + [AB5500_BANK_VIBRA] = { + .bankid = AB5500_BANK_VIBRA, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x10, + .last = 0x13, + }, + }, + }, + [AB5500_BANK_AUDIO_HEADSETUSB] = { + .bankid = AB5500_BANK_AUDIO_HEADSETUSB, + .nranges = 4, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x14, + }, + { + .first = 0x16, + .last = 0x26, + }, + { + .first = 0x28, + .last = 0x30, + }, + { + .first = 0x35, + .last = 0x47, + }, + + }, + }, + [AB5500_BANK_SIM_USBSIM] = { + .bankid = AB5500_BANK_SIM_USBSIM, + .nranges = 3, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x13, + .last = 0x1A, + }, + { + .first = 0x20, + .last = 0x20, + }, + { + .first = 0xFE, + .last = 0xFE, + }, + }, + }, + [AB5500_BANK_VDENC] = { + .bankid = AB5500_BANK_VDENC, + .nranges = 10, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x12, + }, + { + .first = 0x15, + .last = 0x19, + }, + { + .first = 0x1B, + .last = 0x21, + }, + { + .first = 0x27, + .last = 0x2C, + }, + { + .first = 0x41, + .last = 0x41, + }, + { + .first = 0x45, + .last = 0x5B, + }, + { + .first = 0x5D, + .last = 0x5D, + }, + { + .first = 0x69, + .last = 0x69, + }, + { + .first = 0x6C, + .last = 0x6D, + }, + { + .first = 0x80, + .last = 0x81, + }, + }, + }, +}; +static int ab5500_registers_print(struct seq_file *s, void *p) +{ + struct ab5500 *ab = s->private; + unsigned int i; + u8 bank = (u8)ab->debug_bank; + struct ab5500_i2c_ranges *ab5500_reg_ranges; + + if (ab->chip_id == AB5500_2_0) + ab5500_reg_ranges = ab5500v2_reg_ranges; + else + ab5500_reg_ranges = ab5500v1_reg_ranges; + seq_printf(s, AB5500_NAME_STRING " register values:\n"); + for (bank = 0; bank < AB5500_NUM_BANKS; bank++) { + seq_printf(s, " bank %u, %s (0x%x):\n", bank, + bankinfo[bank].name, + bankinfo[bank].slave_addr); + for (i = 0; i < ab5500_reg_ranges[bank].nranges; i++) { + u8 reg; + int err; + + for (reg = ab5500_reg_ranges[bank].range[i].first; + reg <= ab5500_reg_ranges[bank].range[i].last; + reg++) { + u8 value; + + err = get_register_interruptible(ab, bank, reg, + &value); + if (err < 0) { + dev_err(ab->dev, "get_reg failed %d" + "bank 0x%x reg 0x%x\n", + err, bank, reg); + return err; + } + + err = seq_printf(s, "[%d/0x%02X]: 0x%02X\n", + bank, reg, value); + if (err < 0) { + /* + * Error is not returned here since + * the output is wanted in any case + */ + return 0; + } + } + } + } + return 0; +} + +static int ab5500_registers_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab5500_registers_print, inode->i_private); +} + +static const struct file_operations ab5500_registers_fops = { + .open = ab5500_registers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab5500_bank_print(struct seq_file *s, void *p) +{ + struct ab5500 *ab = s->private; + + seq_printf(s, "%d\n", ab->debug_bank); + return 0; +} + +static int ab5500_bank_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab5500_bank_print, inode->i_private); +} + +static ssize_t ab5500_bank_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab5500 *ab = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_bank; + int err; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_bank); + if (err) + return -EINVAL; + + if (user_bank >= AB5500_NUM_BANKS) { + dev_err(ab->dev, + "debugfs error input > number of banks\n"); + return -EINVAL; + } + + ab->debug_bank = user_bank; + + return buf_size; +} + +static int ab5500_address_print(struct seq_file *s, void *p) +{ + struct ab5500 *ab = s->private; + + seq_printf(s, "0x%02X\n", ab->debug_address); + return 0; +} + +static int ab5500_address_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab5500_address_print, inode->i_private); +} + +static ssize_t ab5500_address_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab5500 *ab = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_address; + int err; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_address); + if (err) + return -EINVAL; + if (user_address > 0xff) { + dev_err(ab->dev, + "debugfs error input > 0xff\n"); + return -EINVAL; + } + ab->debug_address = user_address; + return buf_size; +} + +static int ab5500_val_print(struct seq_file *s, void *p) +{ + struct ab5500 *ab = s->private; + int err; + u8 regvalue; + + err = get_register_interruptible(ab, (u8)ab->debug_bank, + (u8)ab->debug_address, ®value); + if (err) { + dev_err(ab->dev, "get_reg failed %d, bank 0x%x" + ", reg 0x%x\n", err, ab->debug_bank, + ab->debug_address); + return -EINVAL; + } + seq_printf(s, "0x%02X\n", regvalue); + + return 0; +} + +static int ab5500_val_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab5500_val_print, inode->i_private); +} + +static ssize_t ab5500_val_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab5500 *ab = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + u8 regvalue; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + if (user_val > 0xff) { + dev_err(ab->dev, + "debugfs error input > 0xff\n"); + return -EINVAL; + } + err = mask_and_set_register_interruptible( + ab, (u8)ab->debug_bank, + (u8)ab->debug_address, 0xFF, (u8)user_val); + if (err) + return -EINVAL; + + get_register_interruptible(ab, (u8)ab->debug_bank, + (u8)ab->debug_address, ®value); + if (err) + return -EINVAL; + + return buf_size; +} + +static const struct file_operations ab5500_bank_fops = { + .open = ab5500_bank_open, + .write = ab5500_bank_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab5500_address_fops = { + .open = ab5500_address_open, + .write = ab5500_address_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab5500_val_fops = { + .open = ab5500_val_open, + .write = ab5500_val_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static struct dentry *ab5500_dir; +static struct dentry *ab5500_reg_file; +static struct dentry *ab5500_bank_file; +static struct dentry *ab5500_address_file; +static struct dentry *ab5500_val_file; + +static inline void ab5500_setup_debugfs(struct ab5500 *ab) +{ + ab->debug_bank = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP; + ab->debug_address = AB5500_CHIP_ID; + + ab5500_dir = debugfs_create_dir(AB5500_NAME_STRING, NULL); + if (!ab5500_dir) + goto exit_no_debugfs; + + ab5500_reg_file = debugfs_create_file("all-bank-registers", + S_IRUGO, ab5500_dir, ab, &ab5500_registers_fops); + if (!ab5500_reg_file) + goto exit_destroy_dir; + + ab5500_bank_file = debugfs_create_file("register-bank", + (S_IRUGO | S_IWUGO), ab5500_dir, ab, &ab5500_bank_fops); + if (!ab5500_bank_file) + goto exit_destroy_reg; + + ab5500_address_file = debugfs_create_file("register-address", + (S_IRUGO | S_IWUGO), ab5500_dir, ab, &ab5500_address_fops); + if (!ab5500_address_file) + goto exit_destroy_bank; + + ab5500_val_file = debugfs_create_file("register-value", + (S_IRUGO | S_IWUGO), ab5500_dir, ab, &ab5500_val_fops); + if (!ab5500_val_file) + goto exit_destroy_address; + + return; + +exit_destroy_address: + debugfs_remove(ab5500_address_file); +exit_destroy_bank: + debugfs_remove(ab5500_bank_file); +exit_destroy_reg: + debugfs_remove(ab5500_reg_file); +exit_destroy_dir: + debugfs_remove(ab5500_dir); +exit_no_debugfs: + dev_err(ab->dev, "failed to create debugfs entries.\n"); + return; +} + +static inline void ab5500_remove_debugfs(void) +{ + debugfs_remove(ab5500_val_file); + debugfs_remove(ab5500_address_file); + debugfs_remove(ab5500_bank_file); + debugfs_remove(ab5500_reg_file); + debugfs_remove(ab5500_dir); +} + +#else /* !CONFIG_DEBUG_FS */ +static inline void ab5500_setup_debugfs(struct ab5500 *ab) +{ +} +static inline void ab5500_remove_debugfs(void) +{ +} +#endif + +/* + * ab5500_setup : Basic set-up, datastructure creation/destruction + * and I2C interface.This sets up a default config + * in the AB5500 chip so that it will work as expected. + * @ab : Pointer to ab5500 structure + * @settings : Pointer to struct abx500_init_settings + * @size : Size of init data + */ +static int __init ab5500_setup(struct ab5500 *ab, + struct abx500_init_settings *settings, unsigned int size) +{ + int err = 0; + int i; + + for (i = 0; i < size; i++) { + err = mask_and_set_register_interruptible(ab, + settings[i].bank, + settings[i].reg, + 0xFF, settings[i].setting); + if (err) + goto exit_no_setup; + + /* If event mask register update the event mask in ab5500 */ + if ((settings[i].bank == AB5500_BANK_IT) && + (AB5500_MASK_BASE <= settings[i].reg) && + (settings[i].reg <= AB5500_MASK_END)) { + ab->mask[settings[i].reg - AB5500_MASK_BASE] = + settings[i].setting; + } + } +exit_no_setup: + return err; +} + +static void ab5500_irq_mask(struct irq_data *data) +{ + struct ab5500 *ab = irq_data_get_irq_chip_data(data); + int offset = data->irq - ab->irq_base; + int index = offset / 8; + int mask = BIT(offset % 8); + + ab->mask[index] |= mask; +} + +static void ab5500_irq_unmask(struct irq_data *data) +{ + struct ab5500 *ab = irq_data_get_irq_chip_data(data); + int offset = data->irq - ab->irq_base; + int index = offset / 8; + int mask = BIT(offset % 8); + + ab->mask[index] &= ~mask; +} + +static void ab5500_irq_lock(struct irq_data *data) +{ + struct ab5500 *ab = irq_data_get_irq_chip_data(data); + + mutex_lock(&ab->irq_lock); +} + +static void ab5500_irq_sync_unlock(struct irq_data *data) +{ + struct ab5500 *ab = irq_data_get_irq_chip_data(data); + int i; + + for (i = 0; i < ab->num_event_reg; i++) { + u8 old = ab->oldmask[i]; + u8 new = ab->mask[i]; + int reg; + + if (new == old) + continue; + + ab->oldmask[i] = new; + + reg = AB5500_IT_MASK0_REG + i; + set_register_interruptible(ab, AB5500_BANK_IT, reg, new); + } + + mutex_unlock(&ab->irq_lock); +} + +static struct irq_chip ab5500_irq_chip = { + .name = "ab5500", + .irq_mask = ab5500_irq_mask, + .irq_unmask = ab5500_irq_unmask, + .irq_bus_lock = ab5500_irq_lock, + .irq_bus_sync_unlock = ab5500_irq_sync_unlock, +}; + +struct ab_family_id { + u8 id; + char *name; +}; + +static const struct ab_family_id ids[] __initdata = { + /* AB5500 */ + { + .id = AB5500_1_0, + .name = "1.0" + }, + { + .id = AB5500_1_1, + .name = "1.1" + }, + { + .id = AB5500_2_0, + .name = "2.0" + }, + /* Terminator */ + { + .id = 0x00, + } +}; + +static int ab5500_irq_init(struct ab5500 *ab) +{ + struct ab5500_platform_data *ab5500_plf_data = + dev_get_platdata(ab->dev); + int i; + unsigned int irq; + + for (i = 0; i < ab5500_plf_data->irq.count; i++) { + + irq = ab5500_plf_data->irq.base + i; + irq_set_chip_data(irq, ab); + irq_set_chip_and_handler(irq, &ab5500_irq_chip, + handle_simple_irq); + irq_set_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + } + return 0; +} + +static void ab5500_irq_remove(struct ab5500 *ab) +{ + struct ab5500_platform_data *ab5500_plf_data = + dev_get_platdata(ab->dev); + int i; + unsigned int irq; + + for (i = 0; i < ab5500_plf_data->irq.count; i++) { + irq = ab5500_plf_data->irq.base + i; +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); + } +} + +static int __init ab5500_probe(struct platform_device *pdev) +{ + struct ab5500 *ab; + struct ab5500_platform_data *ab5500_plf_data = + pdev->dev.platform_data; + struct resource *res; + int err; + int i; + u8 val; + + ab = kzalloc(sizeof(struct ab5500), GFP_KERNEL); + if (!ab) { + dev_err(&pdev->dev, + "could not allocate " AB5500_NAME_STRING " device\n"); + return -ENOMEM; + } + + /* Initialize data structure */ + mutex_init(&ab->access_mutex); + mutex_init(&ab->irq_lock); + ab->dev = &pdev->dev; + ab->irq_base = ab5500_plf_data->irq.base; + + platform_set_drvdata(pdev, ab); + + /* Read chip ID register */ + err = get_register_interruptible(ab, AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + AB5500_CHIP_ID, &ab->chip_id); + if (err) { + dev_err(&pdev->dev, "could not communicate with the analog " + "baseband chip\n"); + goto exit_no_detect; + } + + for (i = 0; ids[i].id != 0x0; i++) { + if (ids[i].id == ab->chip_id) { + snprintf(&ab->chip_name[0], sizeof(ab->chip_name) - 1, + AB5500_ID_FORMAT_STRING, ids[i].name); + break; + } + } + + if (ids[i].id == 0x0) { + dev_err(&pdev->dev, "unknown analog baseband chip id: 0x%x\n", + ab->chip_id); + dev_err(&pdev->dev, "driver not started!\n"); + goto exit_no_detect; + } + + dev_info(&pdev->dev, "detected AB chip: %s\n", &ab->chip_name[0]); + + /* Readout ab->starup_events when prcmu driver is in place */ + ab->startup_events[0] = 0; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "ab5500_platform_get_resource error\n"); + goto exit_no_detect; + } + ab->ab5500_irq = res->start; + + if (ab->chip_id == AB5500_2_0) + ab->num_event_reg = AB5500_NUM_IRQ_REGS; + else + ab->num_event_reg = AB5500_NUM_EVENT_V1_REG; + /* Read the latch regs to know the reason for turn on */ + err = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + 1, &val); + if (err) + goto exit_no_detect; + if (val & RTC_ALARM) /* RTCAlarm */ + turn_on_stat = RTC_ALARM_EVENT; + if (val & POW_KEY_2_ON) /* PonKey2dbR */ + turn_on_stat |= P_ON_KEY2_EVENT; + if (val & POW_KEY_1_ON) /* PonKey1dbR */ + turn_on_stat |= P_ON_KEY1_EVENT; + + err = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + 2, &val); + if (err) + goto exit_no_detect; + if (val & POR_ON_VBAT) + /* PORnVbat */ + turn_on_stat |= POR_ON_VBAT_EVENT ; + err = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + 8, &val); + if (err) + goto exit_no_detect; + if (val & VBUS_DET) + /* VbusDet */ + turn_on_stat |= VBUS_DET_EVENT; + err = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + 18, &val); + if (err) + goto exit_no_detect; + if (val & VBUS_CH_DROP_R) + /* VBUSChDrop */ + turn_on_stat |= VBUS_DET_EVENT; + err = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + 9, &val); + if (err) + goto exit_no_detect; + if (val & USB_CH_DET_DONE) + /* VBUSChDrop */ + turn_on_stat |= VBUS_DET_EVENT; + err = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + 22, &val); + if (err) + goto exit_no_detect; + if (val & USB_CH_DET_DONE) + /* USBLineStatus Change */ + turn_on_stat |= VBUS_DET_EVENT; + + /* Clear and mask all interrupts */ + for (i = 0; i < ab->num_event_reg; i++) { + u8 latchreg = AB5500_IT_LATCH0_REG + i; + u8 maskreg = AB5500_IT_MASK0_REG + i; + + get_register_interruptible(ab, AB5500_BANK_IT, latchreg, &val); + set_register_interruptible(ab, AB5500_BANK_IT, maskreg, 0xff); + ab->mask[i] = ab->oldmask[i] = 0xff; + } + + if (ab->irq_base) { + err = ab5500_irq_init(ab); + if (err) + return err; + + err = request_threaded_irq(res->start, NULL, ab5500_irq, + IRQF_NO_SUSPEND | IRQF_ONESHOT, + "ab5500-core", ab); + if (err) + goto exit_remove_irq; + + } + prcmu_config_abb_event_readout(AB5500_INTERRUPTS); + /* This real unpredictable IRQ is of course sampled for entropy */ + rand_initialize_irq(res->start); + + err = abx500_register_ops(&pdev->dev, &ab5500_ops); + if (err) { + dev_err(&pdev->dev, "ab5500_register ops error\n"); + goto exit_no_irq; + } + + /* Set up and register the platform devices. */ + for (i = 0; i < AB5500_NUM_DEVICES; i++) { + ab5500_devs[i].platform_data = ab5500_plf_data->dev_data[i]; + ab5500_devs[i].pdata_size = ab5500_plf_data->dev_data_sz[i]; + } + + err = mfd_add_devices(&pdev->dev, 0, ab5500_devs, + ARRAY_SIZE(ab5500_devs), NULL, + ab5500_plf_data->irq.base); + if (err) { + dev_err(&pdev->dev, "ab5500_mfd_add_device error\n"); + goto exit_no_add_dev; + } + err = ab5500_setup(ab, ab5500_plf_data->init_settings, + ab5500_plf_data->init_settings_sz); + if (err) { + dev_err(&pdev->dev, "ab5500_setup error\n"); + goto exit_no_add_dev; + } + ab5500_setup_debugfs(ab); + err = sysfs_create_group(&ab->dev->kobj, &ab5500_attr_group); + if (err) { + dev_err(&pdev->dev, "error creating sysfs entries\n"); + goto exit_no_debugfs; + } + + return 0; +exit_no_debugfs: + ab5500_remove_debugfs(); +exit_no_add_dev: + mfd_remove_devices(&pdev->dev); +exit_no_irq: + if (ab->irq_base) { + free_irq(ab->ab5500_irq, ab); +exit_remove_irq: + ab5500_irq_remove(ab); + } +exit_no_detect: + kfree(ab); + return err; +} + +static int __exit ab5500_remove(struct platform_device *pdev) +{ + struct ab5500 *ab = platform_get_drvdata(pdev); + + /* + * At this point, all subscribers should have unregistered + * their notifiers so deactivate IRQ + */ + sysfs_remove_group(&ab->dev->kobj, &ab5500_attr_group); + ab5500_remove_debugfs(); + mfd_remove_devices(&pdev->dev); + if (ab->irq_base) { + free_irq(ab->ab5500_irq, ab); + ab5500_irq_remove(ab); + } + kfree(ab); + return 0; +} + +static struct platform_driver ab5500_driver = { + .driver = { + .name = "ab5500-core", + .owner = THIS_MODULE, + }, + .remove = __exit_p(ab5500_remove), +}; + +static int __init ab5500_core_init(void) +{ + return platform_driver_probe(&ab5500_driver, ab5500_probe); +} + +static void __exit ab5500_core_exit(void) +{ + platform_driver_unregister(&ab5500_driver); +} + +subsys_initcall(ab5500_core_init); +module_exit(ab5500_core_exit); + +MODULE_AUTHOR("Mattias Wallin <mattias.wallin@stericsson.com>"); +MODULE_DESCRIPTION("AB5500 core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/ab5500-gpadc.c b/drivers/mfd/ab5500-gpadc.c new file mode 100644 index 00000000000..6756d3cf37a --- /dev/null +++ b/drivers/mfd/ab5500-gpadc.c @@ -0,0 +1,1180 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Author: Vijaya Kumar K <vijay.kilari@stericsson.com> + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/spinlock.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/mfd/abx500/ab5500-gpadc.h> + +/* + * Manual mode ADC registers + */ +#define AB5500_GPADC_MANUAL_STAT_REG 0x1F +#define AB5500_GPADC_MANDATAL_REG 0x21 +#define AB5500_GPADC_MANDATAH_REG 0x20 +#define AB5500_GPADC_MANUAL_MUX_CTRL 0x22 +#define AB5500_GPADC_MANUAL_MODE_CTRL 0x23 +#define AB5500_GPADC_MANUAL_MODE_CTRL2 0x24 +/* + * Auto/Polling mode ADC registers + */ +#define AB5500_GPADC_AUTO_VBAT_MAX 0x26 +#define AB5500_GPADC_AUTO_VBAT_MIN_TXON 0x27 +#define AB5500_GPADC_AUTO_VBAT_MIN_NOTX 0x28 +#define AB5500_GPADC_AUTO_VBAT_AVGH 0x29 +#define AB5500_GPADC_AUTO_VBAT_AVGL 0x2A +#define AB5500_GPADC_AUTO_ICHAR_MAX 0x2B +#define AB5500_GPADC_AUTO_ICHAR_MIN 0x2C +#define AB5500_GPADC_AUTO_ICHAR_AVG 0x2D +#define AB5500_GPADC_AUTO_CTRL2 0x2F +#define AB5500_GPADC_AUTO_CTRL1 0x30 +#define AB5500_GPADC_AUTO_PWR_CTRL 0x31 +#define AB5500_GPADC_AUTO_TRIG_VBAT_MIN_TXON 0x32 +#define AB5500_GPADC_AUTO_TRIG_VBAT_MIN_NOTX 0x33 +#define AB5500_GPADC_AUTO_TRIG_ADOUT0_CTRL 0x34 +#define AB5500_GPADC_AUTO_TRIG_ADOUT1_CTRL 0x35 +#define AB5500_GPADC_AUTO_TRIG0_MUX_CTRL 0x37 +#define AB5500_GPADC_AUTO_XTALTEMP_CTRL 0x57 +#define AB5500_GPADC_KELVIN_CTRL 0xFE + +/* gpadc constants */ +#define AB5500_INT_ADC_TRIG0 0x0 +#define AB5500_INT_ADC_TRIG1 0x1 +#define AB5500_INT_ADC_TRIG2 0x2 +#define AB5500_INT_ADC_TRIG3 0x3 +#define AB5500_INT_ADC_TRIG4 0x4 +#define AB5500_INT_ADC_TRIG5 0x5 +#define AB5500_INT_ADC_TRIG6 0x6 +#define AB5500_INT_ADC_TRIG7 0x7 + +#define AB5500_GPADC_AUTO_TRIG_INDEX AB5500_GPADC_AUTO_TRIG0_MUX_CTRL +#define GPADC_MANUAL_READY 0x01 +#define GPADC_MANUAL_ADOUT0_MASK 0x30 +#define GPADC_MANUAL_ADOUT1_MASK 0xC0 +#define GPADC_MANUAL_ADOUT0_ON 0x10 +#define GPADC_MANUAL_ADOUT1_ON 0x40 +#define MUX_SCALE_VBAT_MASK 0x02 +#define MUX_SCALE_45 0x02 +#define MUX_SCALE_BDATA_MASK 0x01 +#define MUX_SCALE_BDATA27 0x00 +#define MUX_SCALE_BDATA18 0x01 +#define MUX_SCALE_ACCDET2_MASK 0x01 +#define MUX_SCALE_ACCDET3_MASK 0x02 +#define ACCDET2_SCALE_VOL27 0x00 +#define ACCDET3_SCALE_VOL27 0x00 +#define TRIGX_FREQ_MASK 0x07 +#define AUTO_VBAT_MASK 0x10 +#define AUTO_VBAT_ON 0x10 +#define TRIG_VBAT_TXON_ARM_MASK 0x08 +#define TRIG_VBAT_NOTX_ARM_MASK 0x04 +#define TRIGX_ARM_MASK 0x20 +#define TRIGX_ARM 0x20 +#define TRIGX_MUX_SELECT 0x1F +#define ADC_CAL_OFF_MASK 0x04 +#define ADC_ON_MODE_MASK 0x03 +#define ADC_CAL_ON 0x00 +#define ADC_FULLPWR 0x03 +#define ADC_XTAL_FORCE_MASK 0x80 +#define ADC_XTAL_FORCE_EN 0x80 +#define ADC_XTAL_FORCE_DI 0x00 +#define ADOUT0 0x01 +#define ADOUT1 0x02 +#define MIN_INDEX 0x02 +#define MAX_INDEX 0x03 +#define CTRL_INDEX 0x01 + +/* GPADC constants from AB5500 spec */ +#define BTEMP_MIN 0 +#define BTEMP_MAX 1800 +#define BDATA_MIN 0 +#define BDATA_MAX 2750 +#define PCBTEMP_MIN 0 +#define PCBTEMP_MAX 1800 +#define XTALTEMP_MIN 0 +#define XTALTEMP_MAX 1800 +#define DIETEMP_MIN 0 +#define DIETEMP_MAX 1800 +#define VBUS_I_MIN 0 +#define VBUS_I_MAX 1600 +#define VBUS_V_MIN 0 +#define VBUS_V_MAX 20000 +#define ACCDET2_MIN 0 +#define ACCDET2_MAX 2500 +#define ACCDET3_MIN 0 +#define ACCDET3_MAX 2500 +#define VBAT_MIN 2300 +#define VBAT_MAX 4500 +#define BKBAT_MIN 0 +#define BKBAT_MAX 2750 +#define USBID_MIN 0 +#define USBID_MAX 1800 +#define KELVIN_MIN 0 +#define KELVIN_MAX 4500 + +/* This is used for calibration */ +#define ADC_RESOLUTION 1023 +#define AUTO_ADC_RESOLUTION 255 + +enum adc_auto_channels { + ADC_INPUT_TRIG0 = 0, + ADC_INPUT_TRIG1, + ADC_INPUT_TRIG2, + ADC_INPUT_TRIG3, + ADC_INPUT_TRIG4, + ADC_INPUT_TRIG5, + ADC_INPUT_TRIG6, + ADC_INPUT_TRIG7, + ADC_INPUT_VBAT_TXOFF, + ADC_INPUT_VBAT_TXON, + N_AUTO_TRIGGER +}; + +/** + * struct adc_auto_trigger - AB5500 GPADC auto trigger + * @adc_mux Mux input + * @flag Status of trigger + * @freq Frequency of conversion + * @adout Adout to pull + * @trig_min trigger minimum value + * @trig_max trigger maximum value + * @auto_adc_callback notification callback + */ +struct adc_auto_trigger { + u8 auto_mux; + u8 flag; + u8 freq; + u8 adout; + u8 trig_min; + u8 trig_max; + int (*auto_callb)(int mux); +}; + +/** + * struct ab5500_btemp_interrupts - ab5500 interrupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab5500_adc_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +/** + * struct ab5500_gpadc - AB5500 GPADC device information + * @chip_id ABB chip id + * @dev: pointer to the struct device + * @node: a list of AB5500 GPADCs, hence prepared for + reentrance + * @ab5500_gpadc_complete: pointer to the struct completion, to indicate + * the completion of gpadc conversion + * @ab5500_gpadc_lock: structure of type mutex + * @regu: pointer to the struct regulator + * @irq: interrupt number that is used by gpadc + * @cal_data array of ADC calibration data structs + * @auto_trig auto trigger channel + * @gpadc_trigX_work work items for trigger channels + */ +struct ab5500_gpadc { + u8 chip_id; + struct device *dev; + struct list_head node; + struct mutex ab5500_gpadc_lock; + struct regulator *regu; + int irq; + int prev_bdata; + spinlock_t gpadc_auto_lock; + struct adc_auto_trigger adc_trig[N_AUTO_TRIGGER]; + struct workqueue_struct *gpadc_wq; + struct work_struct gpadc_trig0_work; + struct work_struct gpadc_trig1_work; + struct work_struct gpadc_trig2_work; + struct work_struct gpadc_trig3_work; + struct work_struct gpadc_trig4_work; + struct work_struct gpadc_trig5_work; + struct work_struct gpadc_trig6_work; + struct work_struct gpadc_trig7_work; + struct work_struct gpadc_trig_vbat_txon_work; + struct work_struct gpadc_trig_vbat_txoff_work; +}; + +static LIST_HEAD(ab5500_gpadc_list); + +struct adc_data { + u8 mux; + int min; + int max; + int adout; +}; + +#define ADC_DATA(_id, _mux, _min, _max, _adout) \ + [_id] = { \ + .mux = _mux, \ + .min = _min, \ + .max = _max, \ + .adout = _adout \ + } + +struct adc_data adc_tab[] = { + ADC_DATA(BTEMP_BALL, 0x0D, BTEMP_MIN, BTEMP_MAX, ADOUT0), + ADC_DATA(BAT_CTRL, 0x0D, BDATA_MIN, BDATA_MAX, 0), + ADC_DATA(MAIN_BAT_V, 0x0C, VBAT_MIN, VBAT_MAX, 0), + ADC_DATA(MAIN_BAT_V_TXON, 0x0C, VBAT_MIN, VBAT_MAX, 0), + ADC_DATA(VBUS_V, 0x10, VBUS_V_MIN, VBUS_V_MAX, 0), + ADC_DATA(USB_CHARGER_C, 0x0A, VBUS_I_MIN, VBUS_I_MAX, 0), + ADC_DATA(BK_BAT_V, 0x07, BKBAT_MIN, BKBAT_MAX, 0), + ADC_DATA(DIE_TEMP, 0x0F, DIETEMP_MIN, DIETEMP_MAX, ADOUT0), + ADC_DATA(PCB_TEMP, 0x13, PCBTEMP_MIN, PCBTEMP_MAX, ADOUT0), + ADC_DATA(XTAL_TEMP, 0x06, XTALTEMP_MIN, XTALTEMP_MAX, ADOUT0), + ADC_DATA(USB_ID, 0x1A, USBID_MIN, USBID_MAX, 0), + ADC_DATA(ACC_DETECT2, 0x18, ACCDET2_MIN, ACCDET2_MAX, 0), + ADC_DATA(ACC_DETECT3, 0x19, ACCDET3_MIN, ACCDET3_MAX, 0), + ADC_DATA(MAIN_BAT_V_TRIG_MIN, 0x0C, VBAT_MIN, VBAT_MAX, 0), + ADC_DATA(MAIN_BAT_V_TXON_TRIG_MIN, 0x0C, VBAT_MIN, VBAT_MAX, 0), +}; +/** + * ab5500_gpadc_get() - returns a reference to the primary AB5500 GPADC + * (i.e. the first GPADC in the instance list) + */ +struct ab5500_gpadc *ab5500_gpadc_get(const char *name) +{ + struct ab5500_gpadc *gpadc; + list_for_each_entry(gpadc, &ab5500_gpadc_list, node) { + if (!strcmp(name, dev_name(gpadc->dev))) + return gpadc; + } + + return ERR_PTR(-ENOENT); +} +EXPORT_SYMBOL(ab5500_gpadc_get); + +#define CONV(min, max, x)\ + ((min) + ((((max)-(min))*(x))/ADC_RESOLUTION)) + +static int ab5500_gpadc_ad_to_voltage(struct ab5500_gpadc *gpadc, + u8 in, u16 ad_val) +{ + int res; + + switch (in) { + case PCB_TEMP: + case BTEMP_BALL: + case MAIN_BAT_V: + case MAIN_BAT_V_TXON: + case ACC_DETECT2: + case ACC_DETECT3: + case VBUS_V: + case USB_CHARGER_C: + case BK_BAT_V: + case XTAL_TEMP: + case USB_ID: + case BAT_CTRL: + res = CONV(adc_tab[in].min, adc_tab[in].max, ad_val); + break; + case DIE_TEMP: + /* + * From the AB5500 product specification + * T(deg cel) = 27 - ((ADCode - 709)/2.4213) + * 27 + 709/2.4213 - ADCode/2.4123 + * (320 - ADCode)/2.4213 + */ + res = 320 - (((unsigned long)ad_val * 1000000) / 242130) / 10; + break; + default: + dev_err(gpadc->dev, + "unknown channel, not possible to convert\n"); + res = -EINVAL; + break; + } + return res; +} + +/** + * ab5500_gpadc_convert() - gpadc conversion + * @input: analog input to be converted to digital data + * + * This function converts the selected analog i/p to digital + * data. + */ +int ab5500_gpadc_convert(struct ab5500_gpadc *gpadc, u8 input) +{ + int result, ret = -EINVAL; + u16 data = 0; + u8 looplimit = 0; + u8 status = 0; + u8 low_data, high_data, adout_mask, adout_val; + + if (!gpadc) + return -ENODEV; + + mutex_lock(&gpadc->ab5500_gpadc_lock); + + switch (input) { + case MAIN_BAT_V: + case MAIN_BAT_V_TXON: + /* + * The value of mux scale volatage depends + * on the type of battery + * for LI-ion use MUX_SCALE_35 => 2.3-3.5V + * for LiFePo4 use MUX_SCALE_45 => 2.3-4.5V + * Check type of battery from platform data TODO ??? + */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_VBAT_MASK, MUX_SCALE_45); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to read status\n"); + goto out; + } + break; + case BTEMP_BALL: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_BDATA_MASK, MUX_SCALE_BDATA27); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set mux scale\n"); + goto out; + } + break; + case BAT_CTRL: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_BDATA_MASK, MUX_SCALE_BDATA27); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set mux scale\n"); + goto out; + } + break; + case XTAL_TEMP: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_XTALTEMP_CTRL, + ADC_XTAL_FORCE_MASK, ADC_XTAL_FORCE_EN); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set xtaltemp\n"); + goto out; + } + break; + case ACC_DETECT2: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL2, + MUX_SCALE_ACCDET2_MASK, ACCDET2_SCALE_VOL27); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set accdet2\n"); + goto out; + } + break; + case ACC_DETECT3: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL2, + MUX_SCALE_ACCDET3_MASK, ACCDET3_SCALE_VOL27); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set accdet3\n"); + goto out; + } + break; + case USB_CHARGER_C: + case VBUS_V: + case BK_BAT_V: + case USB_ID: + case PCB_TEMP: + case DIE_TEMP: + break; + default: + dev_err(gpadc->dev, "gpadc: Wrong adc\n"); + goto out; + break; + } + if (adc_tab[input].adout) { + adout_mask = adc_tab[input].adout == ADOUT0 ? + GPADC_MANUAL_ADOUT0_MASK : GPADC_MANUAL_ADOUT1_MASK; + adout_val = adc_tab[input].adout == ADOUT0 ? + GPADC_MANUAL_ADOUT0_ON : GPADC_MANUAL_ADOUT1_ON; + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + adout_mask, adout_val); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set ADOUT\n"); + goto out; + } + } + ret = abx500_set_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_MANUAL_MUX_CTRL, adc_tab[input].mux); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc: fail to trigger manual conv\n"); + goto out; + } + /* wait for completion of conversion */ + looplimit = 0; + do { + msleep(1); + ret = abx500_get_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_STAT_REG, + &status); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to read status\n"); + goto out; + } + if (status & GPADC_MANUAL_READY) + break; + } while (++looplimit < 2); + if (looplimit >= 2) { + dev_err(gpadc->dev, "timeout:failed to complete conversion\n"); + ret = -EINVAL; + goto out; + } + + /* + * Disable ADOUT for measurement + */ + if (adc_tab[input].adout) { + adout_mask = adc_tab[input].adout == ADOUT0 ? + GPADC_MANUAL_ADOUT0_MASK : GPADC_MANUAL_ADOUT1_MASK; + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + adout_mask, 0x0); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to disable ADOUT\n"); + goto out; + } + } + /* + * Disable XTAL TEMP + */ + if (input == XTAL_TEMP) { + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_XTALTEMP_CTRL, + ADC_XTAL_FORCE_MASK, ADC_XTAL_FORCE_DI); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc: fail to disable xtaltemp\n"); + goto out; + } + } + /* Read the converted RAW data */ + ret = abx500_get_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_MANDATAL_REG, &low_data); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: read low data failed\n"); + goto out; + } + + ret = abx500_get_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_MANDATAH_REG, &high_data); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: read high data failed\n"); + goto out; + } + + data = (high_data << 2) | (low_data >> 6); + if (input == BAT_CTRL || input == BTEMP_BALL) { + /* + * TODO: Re-check with h/w team + * discard null or value < 5, as there is some error + * in conversion + */ + if (data < 5) + data = gpadc->prev_bdata; + else + gpadc->prev_bdata = data; + } + result = ab5500_gpadc_ad_to_voltage(gpadc, input, data); + + mutex_unlock(&gpadc->ab5500_gpadc_lock); + return result; + +out: + mutex_unlock(&gpadc->ab5500_gpadc_lock); + dev_err(gpadc->dev, + "gpadc: Failed to AD convert channel %d\n", input); + return ret; +} +EXPORT_SYMBOL(ab5500_gpadc_convert); + +/** + * ab5500_gpadc_program_auto() - gpadc conversion auto conversion + * @trig_index: Generic trigger channel for conversion + * + * This function program the auto trigger channel + */ +static int ab5500_gpadc_program_auto(struct ab5500_gpadc *gpadc, int trig) +{ + int ret; + u8 adout; +#define MIN_INDEX 0x02 +#define MAX_INDEX 0x03 +#define CTRL_INDEX 0x01 + ret = abx500_set_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2) + MIN_INDEX, + gpadc->adc_trig[trig].trig_min); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program min\n"); + return ret; + } + ret = abx500_set_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2) + MAX_INDEX, + gpadc->adc_trig[trig].trig_max); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program max\n"); + return ret; + } + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2), + TRIGX_MUX_SELECT, gpadc->adc_trig[trig].auto_mux); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to select mux\n"); + return ret; + } + if (gpadc->adc_trig[trig].adout) { + adout = gpadc->adc_trig[trig].adout == ADOUT0 ? + gpadc->adc_trig[trig].adout << 6 : + gpadc->adc_trig[trig].adout << 5; + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2) + CTRL_INDEX, + adout, adout); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program adout\n"); + return ret; + } + } + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2) + CTRL_INDEX, + TRIGX_FREQ_MASK, gpadc->adc_trig[trig].freq); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program freq\n"); + return ret; + } + return ret; + +} + +#define TRIG_V(trigval, min, max) \ + ((((trigval) - (min)) * AUTO_ADC_RESOLUTION) / ((max) - (min))) + +static int ab5500_gpadc_vbat_auto_conf(struct ab5500_gpadc *gpadc, + struct adc_auto_input *in) +{ + int trig_min, ret; + u8 trig_reg, trig_arm; + + /* Scale mux voltage */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_VBAT_MASK, MUX_SCALE_45); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to set vbat scale\n"); + return ret; + } + + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_AUTO_CTRL1, + AUTO_VBAT_MASK, AUTO_VBAT_ON); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to set vbat on\n"); + return ret; + } + + trig_min = TRIG_V(in->min, adc_tab[in->mux].min, adc_tab[in->mux].max); + + if (in->mux == MAIN_BAT_V_TRIG_MIN) { + trig_reg = AB5500_GPADC_AUTO_TRIG_VBAT_MIN_NOTX; + trig_arm = TRIG_VBAT_NOTX_ARM_MASK; + } else { + trig_reg = AB5500_GPADC_AUTO_TRIG_VBAT_MIN_TXON; + trig_arm = TRIG_VBAT_TXON_ARM_MASK; + } + ret = abx500_set_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + trig_reg, trig_min); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program vbat min\n"); + return ret; + } + /* + * arm the trigger + */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_CTRL1, trig_arm, trig_arm); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to trig vbat\n"); + return ret; + } + return ret; +} +/** + * ab5500_gpadc_convert_auto() - gpadc conversion + * @auto_input: input trigger for conversion + * + * This function converts the selected channel from + * analog to digital data in auto mode + */ + +int ab5500_gpadc_convert_auto(struct ab5500_gpadc *gpadc, + struct adc_auto_input *in) +{ + int ret, trig; + unsigned long flags; + + if (!gpadc) + return -ENODEV; + mutex_lock(&gpadc->ab5500_gpadc_lock); + + if (in->mux == MAIN_BAT_V_TXON_TRIG_MIN) { + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + if (gpadc->adc_trig[ADC_INPUT_VBAT_TXON].flag == true) { + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + ret = -EBUSY; + dev_err(gpadc->dev, "gpadc: Auto vbat txon busy"); + goto out; + } + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + + ret = ab5500_gpadc_vbat_auto_conf(gpadc, in); + if (ret < 0) + goto out; + + gpadc->adc_trig[ADC_INPUT_VBAT_TXON].auto_mux = in->mux; + gpadc->adc_trig[ADC_INPUT_VBAT_TXON].auto_callb = + in->auto_adc_callback; + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + gpadc->adc_trig[ADC_INPUT_VBAT_TXON].flag = true; + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + } else if (in->mux == MAIN_BAT_V_TRIG_MIN) { + + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + if (gpadc->adc_trig[ADC_INPUT_VBAT_TXOFF].flag == true) { + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + ret = -EBUSY; + dev_err(gpadc->dev, "gpadc: Auto vbat busy"); + goto out; + } + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + + ret = ab5500_gpadc_vbat_auto_conf(gpadc, in); + if (ret < 0) + goto out; + + gpadc->adc_trig[ADC_INPUT_VBAT_TXOFF].auto_mux = in->mux; + gpadc->adc_trig[ADC_INPUT_VBAT_TXOFF].auto_callb = + in->auto_adc_callback; + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + gpadc->adc_trig[ADC_INPUT_VBAT_TXOFF].flag = true; + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + } else { + /* + * check if free trigger is available + */ + trig = ADC_INPUT_TRIG0; + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + while (gpadc->adc_trig[trig].flag == true && + trig <= ADC_INPUT_TRIG7) + trig++; + + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + if (trig > ADC_INPUT_TRIG7) { + ret = -EBUSY; + dev_err(gpadc->dev, "gpadc: no free channel\n"); + goto out; + } + switch (in->mux) { + case BTEMP_BALL: + case MAIN_BAT_V: + /* + * The value of mux scale volatage depends + * on the type of battery + * for LI-ion use MUX_SCALE_35 => 2.3-3.5V + * for LiFePo4 use MUX_SCALE_45 => 2.3-4.5V + * Check type of battery from platform data TODO ??? + */ + ret = abx500_mask_and_set_register_interruptible( + gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_VBAT_MASK, MUX_SCALE_45); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc: failed to read status\n"); + goto out; + } + case ACC_DETECT2: + case ACC_DETECT3: + case VBUS_V: + case USB_CHARGER_C: + case BK_BAT_V: + case PCB_TEMP: + case USB_ID: + case BAT_CTRL: + gpadc->adc_trig[trig].trig_min = + (u8)TRIG_V(in->min, adc_tab[in->mux].min, + adc_tab[in->mux].max); + gpadc->adc_trig[trig].trig_max = + (u8)TRIG_V(in->max, adc_tab[in->mux].min, + adc_tab[in->mux].max); + gpadc->adc_trig[trig].adout = + adc_tab[in->mux].adout; + break; + case DIE_TEMP: + /* + * From the AB5500 product specification + * T(deg_cel) = 27 -(ADCode - 709)/2.4123) + * adc min and max values are based on the above formula. + */ + gpadc->adc_trig[trig].trig_min = + 709 - (22413 * (in->min - 27))/10000; + gpadc->adc_trig[trig].trig_max = + 709 - (22413 * (in->max - 27))/10000; + gpadc->adc_trig[trig].adout = + adc_tab[in->mux].adout; + break; + default: + dev_err(gpadc->dev, "Unknow GPADC request\n"); + break; + } + gpadc->adc_trig[trig].freq = in->freq; + gpadc->adc_trig[trig].auto_mux = + adc_tab[in->mux].mux; + gpadc->adc_trig[trig].auto_callb = in->auto_adc_callback; + + ret = ab5500_gpadc_program_auto(gpadc, trig); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc: fail to program auto ch\n"); + goto out; + } + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig * 4), + TRIGX_ARM_MASK, TRIGX_ARM); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to trigger\n"); + goto out; + } + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + gpadc->adc_trig[trig].flag = true; + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + } +out: + mutex_unlock(&gpadc->ab5500_gpadc_lock); + return ret; + +} +EXPORT_SYMBOL(ab5500_gpadc_convert_auto); + +static void ab5500_gpadc_trigx_work(struct ab5500_gpadc *gp, int trig) +{ + unsigned long flags; + if (gp->adc_trig[trig].auto_callb != NULL) { + gp->adc_trig[trig].auto_callb(gp->adc_trig[trig].auto_mux); + spin_lock_irqsave(&gp->gpadc_auto_lock, flags); + gp->adc_trig[trig].flag = false; + spin_unlock_irqrestore(&gp->gpadc_auto_lock, flags); + } else { + dev_err(gp->dev, "Unknown trig for %d\n", trig); + } +} +/** + * ab5500_gpadc_trig0_work() - work item for trig0 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 0 auto conversion. + */ +static void ab5500_gpadc_trig0_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig0_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG0); +} + +/** + * ab5500_gpadc_trig1_work() - work item for trig1 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig1 auto conversion. + */ +static void ab5500_gpadc_trig1_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig1_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG1); +} + +/** + * ab5500_gpadc_trig2_work() - work item for trig2 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 2 auto conversion. + */ +static void ab5500_gpadc_trig2_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig2_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG2); +} + +/** + * ab5500_gpadc_trig3_work() - work item for trig3 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 3 auto conversion. + */ +static void ab5500_gpadc_trig3_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig3_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG3); +} + +/** + * ab5500_gpadc_trig4_work() - work item for trig4 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 4 auto conversion. + */ +static void ab5500_gpadc_trig4_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig4_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG4); +} + +/** + * ab5500_gpadc_trig5_work() - work item for trig5 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 5 auto conversion. + */ +static void ab5500_gpadc_trig5_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig5_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG5); +} + +/** + * ab5500_gpadc_trig6_work() - work item for trig6 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 6 auto conversion. + */ +static void ab5500_gpadc_trig6_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig6_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG6); +} + +/** + * ab5500_gpadc_trig7_work() - work item for trig7 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 7 auto conversion. + */ +static void ab5500_gpadc_trig7_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig7_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG7); +} + +/** + * ab5500_gpadc_vbat_txon_work() - work item for vbat_txon trigger auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for vbat_txon trigger auto adc. + */ +static void ab5500_gpadc_vbat_txon_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig_vbat_txon_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_VBAT_TXON); +} + +/** + * ab5500_gpadc_vbat_txoff_work() - work item for vbat_txoff trigger auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for vbat_txoff trigger auto adc. + */ +static void ab5500_gpadc_vbat_txoff_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig_vbat_txoff_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_VBAT_TXOFF); +} + +/** + * ab5500_adc_trigx_handler() - isr for auto gpadc conversion trigger + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This is a interrupt service routine for auto gpadc conversion. + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_adc_trigx_handler(int irq, void *_gpadc) +{ + struct ab5500_platform_data *plat; + struct ab5500_gpadc *gpadc = _gpadc; + int dev_irq; + + plat = dev_get_platdata(gpadc->dev->parent); + dev_irq = irq - plat->irq.base; + + switch (dev_irq) { + case AB5500_INT_ADC_TRIG0: + dev_dbg(gpadc->dev, "Trigger 0 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig0_work); + break; + case AB5500_INT_ADC_TRIG1: + dev_dbg(gpadc->dev, "Trigger 1 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig1_work); + break; + case AB5500_INT_ADC_TRIG2: + dev_dbg(gpadc->dev, "Trigger 2 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig2_work); + break; + case AB5500_INT_ADC_TRIG3: + dev_dbg(gpadc->dev, "Trigger 3 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig3_work); + break; + case AB5500_INT_ADC_TRIG4: + dev_dbg(gpadc->dev, "Trigger 4 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig4_work); + break; + case AB5500_INT_ADC_TRIG5: + dev_dbg(gpadc->dev, "Trigger 5 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig5_work); + break; + case AB5500_INT_ADC_TRIG6: + dev_dbg(gpadc->dev, "Trigger 6 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig6_work); + break; + case AB5500_INT_ADC_TRIG7: + dev_dbg(gpadc->dev, "Trigger 7 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig7_work); + break; + default: + dev_dbg(gpadc->dev, "unknown trigx handler input\n"); + break; + } + return IRQ_HANDLED; +} + +/** + * ab5500_adc_vbat_txon_handler() - isr for auto vbat_txon conversion trigger + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This is a interrupt service routine for auto vbat_txon conversion + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_adc_vbat_txon_handler(int irq, void *_gpadc) +{ + struct ab5500_gpadc *gpadc = _gpadc; + + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig_vbat_txon_work); + return IRQ_HANDLED; +} + +/** + * ab5500_adc_vbat_txoff_handler() - isr for auto vbat_txoff conversion trigger + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This is a interrupt service routine for auto vbat_txoff conversion + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_adc_vbat_txoff_handler(int irq, void *_gpadc) +{ + struct ab5500_gpadc *gpadc = _gpadc; + + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig_vbat_txoff_work); + return IRQ_HANDLED; +} + +/** + * ab5500_gpadc_configuration() - function for gpadc conversion + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This function configures the gpadc + */ +static int ab5500_gpadc_configuration(struct ab5500_gpadc *gpadc) +{ + int ret; + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_CTRL2, + ADC_CAL_OFF_MASK | ADC_ON_MODE_MASK, + ADC_CAL_ON | ADC_FULLPWR); + return ret; +} + +/* ab5500 btemp driver interrupts and their respective isr */ +static struct ab5500_adc_interrupts ab5500_adc_irq[] = { + {"TRIGGER-0", ab5500_adc_trigx_handler}, + {"TRIGGER-1", ab5500_adc_trigx_handler}, + {"TRIGGER-2", ab5500_adc_trigx_handler}, + {"TRIGGER-3", ab5500_adc_trigx_handler}, + {"TRIGGER-4", ab5500_adc_trigx_handler}, + {"TRIGGER-5", ab5500_adc_trigx_handler}, + {"TRIGGER-6", ab5500_adc_trigx_handler}, + {"TRIGGER-7", ab5500_adc_trigx_handler}, + {"TRIGGER-VBAT-TXON", ab5500_adc_vbat_txon_handler}, + {"TRIGGER-VBAT", ab5500_adc_vbat_txoff_handler}, +}; + +static int __devinit ab5500_gpadc_probe(struct platform_device *pdev) +{ + int ret, irq, i, j; + struct ab5500_gpadc *gpadc; + + gpadc = kzalloc(sizeof(struct ab5500_gpadc), GFP_KERNEL); + if (!gpadc) { + dev_err(&pdev->dev, "Error: No memory\n"); + return -ENOMEM; + } + gpadc->dev = &pdev->dev; + mutex_init(&gpadc->ab5500_gpadc_lock); + spin_lock_init(&gpadc->gpadc_auto_lock); + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_adc_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_adc_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab5500_adc_irq[i].isr, + IRQF_NO_SUSPEND, + ab5500_adc_irq[i].name, gpadc); + + if (ret) { + dev_err(gpadc->dev, "failed to request %s IRQ %d: %d\n" + , ab5500_adc_irq[i].name, irq, ret); + goto fail_irq; + } + dev_dbg(gpadc->dev, "Requested %s IRQ %d: %d\n", + ab5500_adc_irq[i].name, irq, ret); + } + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(gpadc->dev); + if (ret < 0) { + dev_err(gpadc->dev, "failed to get chip ID\n"); + goto fail_irq; + } + gpadc->chip_id = (u8) ret; + + /* Create a work queue for gpadc auto */ + gpadc->gpadc_wq = + create_singlethread_workqueue("ab5500_gpadc_wq"); + if (gpadc->gpadc_wq == NULL) { + dev_err(gpadc->dev, "failed to create work queue\n"); + goto fail_irq; + } + + INIT_WORK(&gpadc->gpadc_trig0_work, ab5500_gpadc_trig0_work); + INIT_WORK(&gpadc->gpadc_trig1_work, ab5500_gpadc_trig1_work); + INIT_WORK(&gpadc->gpadc_trig2_work, ab5500_gpadc_trig2_work); + INIT_WORK(&gpadc->gpadc_trig3_work, ab5500_gpadc_trig3_work); + INIT_WORK(&gpadc->gpadc_trig4_work, ab5500_gpadc_trig4_work); + INIT_WORK(&gpadc->gpadc_trig5_work, ab5500_gpadc_trig5_work); + INIT_WORK(&gpadc->gpadc_trig6_work, ab5500_gpadc_trig6_work); + INIT_WORK(&gpadc->gpadc_trig7_work, ab5500_gpadc_trig7_work); + INIT_WORK(&gpadc->gpadc_trig_vbat_txon_work, + ab5500_gpadc_vbat_txon_work); + INIT_WORK(&gpadc->gpadc_trig_vbat_txoff_work, + ab5500_gpadc_vbat_txoff_work); + + for (j = 0; j < N_AUTO_TRIGGER; j++) + gpadc->adc_trig[j].flag = false; + + ret = ab5500_gpadc_configuration(gpadc); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: configuration failed\n"); + goto free_wq; + } + list_add_tail(&gpadc->node, &ab5500_gpadc_list); + + return 0; +free_wq: + destroy_workqueue(gpadc->gpadc_wq); +fail_irq: + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab5500_adc_irq[i].name); + free_irq(irq, gpadc); + } + kfree(gpadc); + gpadc = NULL; + return ret; +} + +static int __devexit ab5500_gpadc_remove(struct platform_device *pdev) +{ + int i, irq; + struct ab5500_gpadc *gpadc = platform_get_drvdata(pdev); + + /* remove this gpadc entry from the list */ + list_del(&gpadc->node); + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_adc_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_adc_irq[i].name); + free_irq(irq, gpadc); + } + /* Flush work */ + flush_workqueue(gpadc->gpadc_wq); + + /* Delete the work queue */ + destroy_workqueue(gpadc->gpadc_wq); + + kfree(gpadc); + gpadc = NULL; + return 0; +} + +static struct platform_driver ab5500_gpadc_driver = { + .probe = ab5500_gpadc_probe, + .remove = __devexit_p(ab5500_gpadc_remove), + .driver = { + .name = "ab5500-adc", + .owner = THIS_MODULE, + }, +}; + +static int __init ab5500_gpadc_init(void) +{ + return platform_driver_register(&ab5500_gpadc_driver); +} + +static void __exit ab5500_gpadc_exit(void) +{ + platform_driver_unregister(&ab5500_gpadc_driver); +} + +subsys_initcall_sync(ab5500_gpadc_init); +module_exit(ab5500_gpadc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Vijaya Kumar K"); +MODULE_ALIAS("platform:ab5500_adc"); +MODULE_DESCRIPTION("AB5500 GPADC driver"); diff --git a/drivers/mfd/ab5500-power.c b/drivers/mfd/ab5500-power.c new file mode 100644 index 00000000000..a549b3e7538 --- /dev/null +++ b/drivers/mfd/ab5500-power.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/init.h> +#include <linux/platform_device.h> + +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> + +static struct device *dev; + +/* STARTUP */ +#define AB5500_SYSPOR_CONTROL 0x30 + +/* VINT IO I2C CLOCK */ +#define AB5500_RTC_VINT 0x01 + +int ab5500_clock_rtc_enable(int num, bool enable) +{ + /* RTC_CLK{0,1,2} are bits {4,3,2}, active low */ + u8 mask = BIT(4 - num); + u8 value = enable ? 0 : mask; + + /* Don't allow RTC_CLK0 to be controlled. */ + if (num < 1 || num > 2) + return -EINVAL; + + if (!dev) + return -EAGAIN; + + return abx500_mask_and_set(dev, AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + AB5500_RTC_VINT, mask, value); +} + +static void ab5500_power_off(void) +{ + sigset_t old; + sigset_t all; + + sigfillset(&all); + + if (!sigprocmask(SIG_BLOCK, &all, &old)) { + /* Clear dbb_on */ + int ret = abx500_set(dev, AB5500_BANK_STARTUP, + AB5500_SYSPOR_CONTROL, 0); + WARN_ON(ret); + } +} + +static int __devinit ab5500_power_probe(struct platform_device *pdev) +{ + struct ab5500_platform_data *plat = dev_get_platdata(pdev->dev.parent); + + dev = &pdev->dev; + + if (plat->pm_power_off) + pm_power_off = ab5500_power_off; + + return 0; +} + +static int __devexit ab5500_power_remove(struct platform_device *pdev) +{ + struct ab5500_platform_data *plat = dev_get_platdata(pdev->dev.parent); + + if (plat->pm_power_off) + pm_power_off = NULL; + dev = NULL; + + return 0; +} + +static struct platform_driver ab5500_power_driver = { + .driver = { + .name = "ab5500-power", + .owner = THIS_MODULE, + }, + .probe = ab5500_power_probe, + .remove = __devexit_p(ab5500_power_remove), +}; + +static int __init ab8500_sysctrl_init(void) +{ + return platform_driver_register(&ab5500_power_driver); +} + +subsys_initcall(ab8500_sysctrl_init); + +MODULE_DESCRIPTION("AB5500 power driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index 387705e494b..3ce5dfe1378 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -92,6 +92,8 @@ #define AB8500_REV_REG 0x80 #define AB8500_SWITCH_OFF_STATUS 0x00 +#define AB8500_TURN_ON_STATUS 0x00 + /* * Map interrupt numbers to the LATCH and MASK register offsets, Interrupt * numbers are indexed into this array with (num / 8). @@ -293,6 +295,7 @@ static struct irq_chip ab8500_irq_chip = { .irq_bus_lock = ab8500_irq_lock, .irq_bus_sync_unlock = ab8500_irq_sync_unlock, .irq_mask = ab8500_irq_mask, + .irq_disable = ab8500_irq_mask, .irq_unmask = ab8500_irq_unmask, }; @@ -679,7 +682,7 @@ static struct resource __devinitdata ab8500_usb_resources[] = { static struct resource __devinitdata ab8500_temp_resources[] = { { - .name = "AB8500_TEMP_WARM", + .name = "ABX500_TEMP_WARM", .start = AB8500_INT_TEMP_WARM, .end = AB8500_INT_TEMP_WARM, .flags = IORESOURCE_IRQ, @@ -701,6 +704,9 @@ static struct mfd_cell __devinitdata ab8500_devs[] = { .name = "ab8500-regulator", }, { + .name = "ab8500-regulator-debug", + }, + { .name = "ab8500-gpio", .num_resources = ARRAY_SIZE(ab8500_gpio_resources), .resources = ab8500_gpio_resources, @@ -770,7 +776,7 @@ static struct mfd_cell __devinitdata ab8500_devs[] = { .name = "ab8500-denc", }, { - .name = "ab8500-temp", + .name = "abx500-temp", .num_resources = ARRAY_SIZE(ab8500_temp_resources), .resources = ab8500_temp_resources, }, @@ -811,12 +817,40 @@ static ssize_t show_switch_off_status(struct device *dev, return sprintf(buf, "%#x\n", value); } +/* + * ab8500 has turned on due to (TURN_ON_STATUS): + * 0x01 PORnVbat + * 0x02 PonKey1dbF + * 0x04 PonKey2dbF + * 0x08 RTCAlarm + * 0x10 MainChDet + * 0x20 VbusDet + * 0x40 UsbIDDetect + * 0x80 Reserved + */ +static ssize_t show_turn_on_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + u8 value; + struct ab8500 *ab8500; + + ab8500 = dev_get_drvdata(dev); + ret = get_register_interruptible(ab8500, AB8500_SYS_CTRL1_BLOCK, + AB8500_TURN_ON_STATUS, &value); + if (ret < 0) + return ret; + return sprintf(buf, "%#x\n", value); +} + static DEVICE_ATTR(chip_id, S_IRUGO, show_chip_id, NULL); static DEVICE_ATTR(switch_off_status, S_IRUGO, show_switch_off_status, NULL); +static DEVICE_ATTR(turn_on_status, S_IRUGO, show_turn_on_status, NULL); static struct attribute *ab8500_sysfs_entries[] = { &dev_attr_chip_id.attr, &dev_attr_switch_off_status.attr, + &dev_attr_turn_on_status.attr, NULL, }; @@ -843,11 +877,11 @@ int __devinit ab8500_init(struct ab8500 *ab8500) return ret; switch (value) { - case AB8500_CUTEARLY: case AB8500_CUT1P0: case AB8500_CUT1P1: case AB8500_CUT2P0: case AB8500_CUT3P0: + case AB8500_CUT3P3: dev_info(ab8500->dev, "detected chip, revision: %#x\n", value); break; default: diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index 64bdeeb1c11..6e62714bcb6 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -4,19 +4,101 @@ * Author: Mattias Wallin <mattias.wallin@stericsson.com> for ST-Ericsson. * License Terms: GNU General Public License v2 */ +/* + * AB8500 register access + * ====================== + * + * read: + * # echo BANK > <debugfs>/ab8500/register-bank + * # echo ADDR > <debugfs>/ab8500/register-address + * # cat <debugfs>/ab8500/register-value + * + * write: + * # echo BANK > <debugfs>/ab8500/register-bank + * # echo ADDR > <debugfs>/ab8500/register-address + * # echo VALUE > <debugfs>/ab8500/register-value + * + * read all registers from a bank: + * # echo BANK > <debugfs>/ab8500/register-bank + * # cat <debugfs>/ab8500/all-bank-register + * + * BANK target AB8500 register bank + * ADDR target AB8500 register address + * VALUE decimal or 0x-prefixed hexadecimal + * + * + * User Space notification on AB8500 IRQ + * ===================================== + * + * Allows user space entity to be notified when target AB8500 IRQ occurs. + * When subscribed, a sysfs entry is created in ab8500.i2c platform device. + * One can pool this file to get target IRQ occurence information. + * + * subscribe to an AB8500 IRQ: + * # echo IRQ > <debugfs>/ab8500/irq-subscribe + * + * unsubscribe from an AB8500 IRQ: + * # echo IRQ > <debugfs>/ab8500/irq-unsubscribe + * + * + * AB8500 register formated read/write access + * ========================================== + * + * Read: read data, data>>SHIFT, data&=MASK, output data + * [0xABCDEF98] shift=12 mask=0xFFF => 0x00000CDE + * Write: read data, data &= ~(MASK<<SHIFT), data |= (VALUE<<SHIFT), write data + * [0xABCDEF98] shift=12 mask=0xFFF value=0x123 => [0xAB123F98] + * + * Usage: + * # echo "CMD [OPTIONS] BANK ADRESS [VALUE]" > $debugfs/ab8500/hwreg + * + * CMD read read access + * write write access + * + * BANK target reg bank + * ADDRESS target reg address + * VALUE (write) value to be updated + * + * OPTIONS + * -d|-dec (read) output in decimal + * -h|-hexa (read) output in 0x-hexa (default) + * -l|-w|-b 32bit (default), 16bit or 8bit reg access + * -m|-mask MASK 0x-hexa mask (default 0xFFFFFFFF) + * -s|-shift SHIFT bit shift value (read:left, write:right) + * -o|-offset OFFSET address offset to add to ADDRESS value + * + * Warning: bit shift operation is applied to bit-mask. + * Warning: bit shift direction depends on read or right command. + */ #include <linux/seq_file.h> #include <linux/uaccess.h> #include <linux/fs.h> #include <linux/debugfs.h> #include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/kobject.h> +#include <linux/slab.h> #include <linux/mfd/abx500.h> #include <linux/mfd/ab8500.h> +#include <linux/mfd/ab8500/gpadc.h> + +#ifdef CONFIG_DEBUG_FS +#include <linux/string.h> +#include <linux/ctype.h> +#endif static u32 debug_bank; static u32 debug_address; +static int irq_first; +static int irq_last; +static u32 irq_count[AB8500_NR_IRQS]; + +static struct device_attribute *dev_attr[AB8500_NR_IRQS]; +static char *event_name[AB8500_NR_IRQS]; + /** * struct ab8500_reg_range * @first: the first address of the range @@ -41,15 +123,35 @@ struct ab8500_i2c_ranges { const struct ab8500_reg_range *range; }; +/* hwreg- "mask" and "shift" entries ressources */ +struct hwreg_cfg { + u32 bank; /* target bank */ + u32 addr; /* target address */ + uint fmt; /* format */ + uint mask; /* read/write mask, applied before any bit shift */ + int shift; /* bit shift (read:right shift, write:left shift */ +}; +/* fmt bit #0: 0=hexa, 1=dec */ +#define REG_FMT_DEC(c) ((c)->fmt & 0x1) +#define REG_FMT_HEX(c) (!REG_FMT_DEC(c)) + +static struct hwreg_cfg hwreg_cfg = { + .addr = 0, /* default: invalid phys addr */ + .fmt = 0, /* default: 32bit access, hex output */ + .mask = 0xFFFFFFFF, /* default: no mask */ + .shift = 0, /* default: no bit shift */ +}; + #define AB8500_NAME_STRING "ab8500" -#define AB8500_NUM_BANKS 22 +#define AB8500_ADC_NAME_STRING "gpadc" +#define AB8500_NUM_BANKS 24 #define AB8500_REV_REG 0x80 static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { [0x0] = { .num_ranges = 0, - .range = 0, + .range = NULL, }, [AB8500_SYS_CTRL1_BLOCK] = { .num_ranges = 3, @@ -214,7 +316,7 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { }, }, [AB8500_CHARGER] = { - .num_ranges = 8, + .num_ranges = 9, .range = (struct ab8500_reg_range[]) { { .first = 0x00, @@ -248,6 +350,10 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { .first = 0xC0, .last = 0xC2, }, + { + .first = 0xf5, + .last = 0xf6, + }, }, }, [AB8500_GAS_GAUGE] = { @@ -267,6 +373,24 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { }, }, }, + [AB8500_DEVELOPMENT] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + }, + }, + [AB8500_DEBUG] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x05, + .last = 0x07, + }, + }, + }, [AB8500_AUDIO] = { .num_ranges = 1, .range = (struct ab8500_reg_range[]) { @@ -353,6 +477,24 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { }, }; +static irqreturn_t ab8500_debug_handler(int irq, void *data) +{ + char buf[16]; + struct kobject *kobj = (struct kobject *)data; + unsigned int irq_abb = irq - irq_first; + + if (irq_abb < AB8500_NR_IRQS) + irq_count[irq_abb]++; + /* + * This makes it possible to use poll for events (POLLPRI | POLLERR) + * from userspace on sysfs file named <irq-nr> + */ + sprintf(buf, "%d", irq); + sysfs_notify(kobj, NULL, buf); + + return IRQ_HANDLED; +} + static int ab8500_registers_print(struct seq_file *s, void *p) { struct device *dev = s->private; @@ -514,10 +656,732 @@ static ssize_t ab8500_val_write(struct file *file, printk(KERN_ERR "abx500_set_reg failed %d, %d", err, __LINE__); return -EINVAL; } + return count; +} + +/* + * - HWREG DB8500 formated routines + */ +static int ab8500_hwreg_print(struct seq_file *s, void *d) +{ + struct device *dev = s->private; + int ret; + u8 regvalue; + + ret = abx500_get_register_interruptible(dev, + (u8)hwreg_cfg.bank, (u8)hwreg_cfg.addr, ®value); + if (ret < 0) { + dev_err(dev, "abx500_get_reg fail %d, %d\n", + ret, __LINE__); + return -EINVAL; + } + + if (hwreg_cfg.shift >= 0) + regvalue >>= hwreg_cfg.shift; + else + regvalue <<= -hwreg_cfg.shift; + regvalue &= hwreg_cfg.mask; + + if (REG_FMT_DEC(&hwreg_cfg)) + seq_printf(s, "%d\n", regvalue); + else + seq_printf(s, "0x%02X\n", regvalue); + return 0; +} + +static int ab8500_hwreg_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_hwreg_print, inode->i_private); +} + +static int ab8500_gpadc_bat_ctrl_print(struct seq_file *s, void *p) +{ + int bat_ctrl_raw; + int bat_ctrl_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + bat_ctrl_raw = ab8500_gpadc_read_raw(gpadc, BAT_CTRL); + bat_ctrl_convert = ab8500_gpadc_ad_to_voltage(gpadc, + BAT_CTRL, bat_ctrl_raw); + + return seq_printf(s, "%d,0x%X\n", + bat_ctrl_convert, bat_ctrl_raw); +} + +static int ab8500_gpadc_bat_ctrl_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_bat_ctrl_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_bat_ctrl_fops = { + .open = ab8500_gpadc_bat_ctrl_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_btemp_ball_print(struct seq_file *s, void *p) +{ + int btemp_ball_raw; + int btemp_ball_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + btemp_ball_raw = ab8500_gpadc_read_raw(gpadc, BTEMP_BALL); + btemp_ball_convert = ab8500_gpadc_ad_to_voltage(gpadc, BTEMP_BALL, + btemp_ball_raw); + + return seq_printf(s, + "%d,0x%X\n", btemp_ball_convert, btemp_ball_raw); +} + +static int ab8500_gpadc_btemp_ball_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_btemp_ball_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_btemp_ball_fops = { + .open = ab8500_gpadc_btemp_ball_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_main_charger_v_print(struct seq_file *s, void *p) +{ + int main_charger_v_raw; + int main_charger_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + main_charger_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_V); + main_charger_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, + MAIN_CHARGER_V, main_charger_v_raw); + + return seq_printf(s, "%d,0x%X\n", + main_charger_v_convert, main_charger_v_raw); +} + +static int ab8500_gpadc_main_charger_v_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_main_charger_v_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_main_charger_v_fops = { + .open = ab8500_gpadc_main_charger_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_acc_detect1_print(struct seq_file *s, void *p) +{ + int acc_detect1_raw; + int acc_detect1_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + acc_detect1_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT1); + acc_detect1_convert = ab8500_gpadc_ad_to_voltage(gpadc, ACC_DETECT1, + acc_detect1_raw); + + return seq_printf(s, "%d,0x%X\n", + acc_detect1_convert, acc_detect1_raw); +} + +static int ab8500_gpadc_acc_detect1_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_acc_detect1_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_acc_detect1_fops = { + .open = ab8500_gpadc_acc_detect1_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_acc_detect2_print(struct seq_file *s, void *p) +{ + int acc_detect2_raw; + int acc_detect2_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + acc_detect2_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT2); + acc_detect2_convert = ab8500_gpadc_ad_to_voltage(gpadc, + ACC_DETECT2, acc_detect2_raw); + + return seq_printf(s, "%d,0x%X\n", + acc_detect2_convert, acc_detect2_raw); +} + +static int ab8500_gpadc_acc_detect2_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_acc_detect2_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_acc_detect2_fops = { + .open = ab8500_gpadc_acc_detect2_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_aux1_print(struct seq_file *s, void *p) +{ + int aux1_raw; + int aux1_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + aux1_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX1); + aux1_convert = ab8500_gpadc_ad_to_voltage(gpadc, ADC_AUX1, + aux1_raw); + + return seq_printf(s, "%d,0x%X\n", + aux1_convert, aux1_raw); +} + +static int ab8500_gpadc_aux1_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_aux1_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_aux1_fops = { + .open = ab8500_gpadc_aux1_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_aux2_print(struct seq_file *s, void *p) +{ + int aux2_raw; + int aux2_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + aux2_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX2); + aux2_convert = ab8500_gpadc_ad_to_voltage(gpadc, ADC_AUX2, + aux2_raw); + + return seq_printf(s, "%d,0x%X\n", + aux2_convert, aux2_raw); +} + +static int ab8500_gpadc_aux2_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_aux2_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_aux2_fops = { + .open = ab8500_gpadc_aux2_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_main_bat_v_print(struct seq_file *s, void *p) +{ + int main_bat_v_raw; + int main_bat_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + main_bat_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_BAT_V); + main_bat_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, MAIN_BAT_V, + main_bat_v_raw); + + return seq_printf(s, "%d,0x%X\n", + main_bat_v_convert, main_bat_v_raw); +} + +static int ab8500_gpadc_main_bat_v_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_main_bat_v_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_main_bat_v_fops = { + .open = ab8500_gpadc_main_bat_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_vbus_v_print(struct seq_file *s, void *p) +{ + int vbus_v_raw; + int vbus_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + vbus_v_raw = ab8500_gpadc_read_raw(gpadc, VBUS_V); + vbus_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, VBUS_V, + vbus_v_raw); + + return seq_printf(s, "%d,0x%X\n", + vbus_v_convert, vbus_v_raw); +} + +static int ab8500_gpadc_vbus_v_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_vbus_v_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_vbus_v_fops = { + .open = ab8500_gpadc_vbus_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_main_charger_c_print(struct seq_file *s, void *p) +{ + int main_charger_c_raw; + int main_charger_c_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + main_charger_c_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_C); + main_charger_c_convert = ab8500_gpadc_ad_to_voltage(gpadc, + MAIN_CHARGER_C, main_charger_c_raw); + + return seq_printf(s, "%d,0x%X\n", + main_charger_c_convert, main_charger_c_raw); +} + +static int ab8500_gpadc_main_charger_c_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_main_charger_c_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_main_charger_c_fops = { + .open = ab8500_gpadc_main_charger_c_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_usb_charger_c_print(struct seq_file *s, void *p) +{ + int usb_charger_c_raw; + int usb_charger_c_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + usb_charger_c_raw = ab8500_gpadc_read_raw(gpadc, USB_CHARGER_C); + usb_charger_c_convert = ab8500_gpadc_ad_to_voltage(gpadc, + USB_CHARGER_C, usb_charger_c_raw); + + return seq_printf(s, "%d,0x%X\n", + usb_charger_c_convert, usb_charger_c_raw); +} + +static int ab8500_gpadc_usb_charger_c_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_usb_charger_c_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_usb_charger_c_fops = { + .open = ab8500_gpadc_usb_charger_c_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_bk_bat_v_print(struct seq_file *s, void *p) +{ + int bk_bat_v_raw; + int bk_bat_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + bk_bat_v_raw = ab8500_gpadc_read_raw(gpadc, BK_BAT_V); + bk_bat_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, + BK_BAT_V, bk_bat_v_raw); + + return seq_printf(s, "%d,0x%X\n", + bk_bat_v_convert, bk_bat_v_raw); +} + +static int ab8500_gpadc_bk_bat_v_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_bk_bat_v_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_bk_bat_v_fops = { + .open = ab8500_gpadc_bk_bat_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_die_temp_print(struct seq_file *s, void *p) +{ + int die_temp_raw; + int die_temp_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + die_temp_raw = ab8500_gpadc_read_raw(gpadc, DIE_TEMP); + die_temp_convert = ab8500_gpadc_ad_to_voltage(gpadc, DIE_TEMP, + die_temp_raw); + + return seq_printf(s, "%d,0x%X\n", + die_temp_convert, die_temp_raw); +} + +static int ab8500_gpadc_die_temp_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_die_temp_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_die_temp_fops = { + .open = ab8500_gpadc_die_temp_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +/* + * return length of an ASCII numerical value, 0 is string is not a + * numerical value. + * string shall start at value 1st char. + * string can be tailed with \0 or space or newline chars only. + * value can be decimal or hexadecimal (prefixed 0x or 0X). + */ +static int strval_len(char *b) +{ + char *s = b; + if ((*s == '0') && ((*(s+1) == 'x') || (*(s+1) == 'X'))) { + s += 2; + for (; *s && (*s != ' ') && (*s != '\n'); s++) { + if (!isxdigit(*s)) + return 0; + } + } else { + if (*s == '-') + s++; + for (; *s && (*s != ' ') && (*s != '\n'); s++) { + if (!isdigit(*s)) + return 0; + } + } + return (int) (s-b); +} + +/* + * parse hwreg input data. + * update global hwreg_cfg only if input data syntax is ok. + */ +static ssize_t hwreg_common_write(char *b, struct hwreg_cfg *cfg, + struct device *dev) +{ + uint write, val = 0; + struct hwreg_cfg loc = { + .bank = 0, /* default: invalid phys addr */ + .addr = 0, /* default: invalid phys addr */ + .fmt = 0, /* default: 32bit access, hex output */ + .mask = 0xFFFFFFFF, /* default: no mask */ + .shift = 0, /* default: no bit shift */ + }; + + /* read or write ? */ + if (!strncmp(b, "read ", 5)) { + write = 0; + b += 5; + } else if (!strncmp(b, "write ", 6)) { + write = 1; + b += 6; + } else + return -EINVAL; + + /* OPTIONS -l|-w|-b -s -m -o */ + while ((*b == ' ') || (*b == '-')) { + if (*(b-1) != ' ') { + b++; + continue; + } + if ((!strncmp(b, "-d ", 3)) || + (!strncmp(b, "-dec ", 5))) { + b += (*(b+2) == ' ') ? 3 : 5; + loc.fmt |= (1<<0); + } else if ((!strncmp(b, "-h ", 3)) || + (!strncmp(b, "-hex ", 5))) { + b += (*(b+2) == ' ') ? 3 : 5; + loc.fmt &= ~(1<<0); + } else if ((!strncmp(b, "-m ", 3)) || + (!strncmp(b, "-mask ", 6))) { + b += (*(b+2) == ' ') ? 3 : 6; + if (strval_len(b) == 0) + return -EINVAL; + loc.mask = simple_strtoul(b, &b, 0); + } else if ((!strncmp(b, "-s ", 3)) || + (!strncmp(b, "-shift ", 7))) { + b += (*(b+2) == ' ') ? 3 : 7; + if (strval_len(b) == 0) + return -EINVAL; + loc.shift = simple_strtol(b, &b, 0); + } else { + return -EINVAL; + } + } + /* get arg BANK and ADDRESS */ + if (strval_len(b) == 0) + return -EINVAL; + loc.bank = simple_strtoul(b, &b, 0); + while (*b == ' ') + b++; + if (strval_len(b) == 0) + return -EINVAL; + loc.addr = simple_strtoul(b, &b, 0); + + if (write) { + while (*b == ' ') + b++; + if (strval_len(b) == 0) + return -EINVAL; + val = simple_strtoul(b, &b, 0); + } + + /* args are ok, update target cfg (mainly for read) */ + *cfg = loc; + +#ifdef ABB_HWREG_DEBUG + pr_warn("HWREG request: %s, %s, addr=0x%08X, mask=0x%X, shift=%d" + "value=0x%X\n", (write) ? "write" : "read", + REG_FMT_DEC(cfg) ? "decimal" : "hexa", + cfg->addr, cfg->mask, cfg->shift, val); +#endif + + if (write) { + u8 regvalue; + int ret = abx500_get_register_interruptible(dev, + (u8)cfg->bank, (u8)cfg->addr, ®value); + if (ret < 0) { + dev_err(dev, "abx500_get_reg fail %d, %d\n", + ret, __LINE__); + return -EINVAL; + } + + if (cfg->shift >= 0) { + regvalue &= ~(cfg->mask << (cfg->shift)); + val = (val & cfg->mask) << (cfg->shift); + } else { + regvalue &= ~(cfg->mask >> (-cfg->shift)); + val = (val & cfg->mask) >> (-cfg->shift); + } + val = val | regvalue; + + ret = abx500_set_register_interruptible(dev, + (u8)cfg->bank, (u8)cfg->addr, (u8)val); + if (ret < 0) { + pr_err("abx500_set_reg failed %d, %d", ret, __LINE__); + return -EINVAL; + } + + } + return 0; +} + +static ssize_t ab8500_hwreg_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + char buf[128]; + int buf_size, ret; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + /* get args and process */ + ret = hwreg_common_write(buf, &hwreg_cfg, dev); + return (ret) ? ret : buf_size; +} + +/* + * - irq subscribe/unsubscribe stuff + */ +static int ab8500_subscribe_unsubscribe_print(struct seq_file *s, void *p) +{ + seq_printf(s, "%d\n", irq_first); + + return 0; +} + +static int ab8500_subscribe_unsubscribe_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_subscribe_unsubscribe_print, + inode->i_private); +} + +/* + * Userspace should use poll() on this file. When an event occur + * the blocking poll will be released. + */ +static ssize_t show_irq(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long name; + unsigned int irq_index; + int err; + + err = strict_strtoul(attr->attr.name, 0, &name); + if (err) + return err; + + irq_index = name - irq_first; + if (irq_index >= AB8500_NR_IRQS) + return -EINVAL; + else + return sprintf(buf, "%u\n", irq_count[irq_index]); +} + +static ssize_t ab8500_subscribe_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + unsigned int irq_index; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + if (user_val < irq_first) { + dev_err(dev, "debugfs error input < %d\n", irq_first); + return -EINVAL; + } + if (user_val > irq_last) { + dev_err(dev, "debugfs error input > %d\n", irq_last); + return -EINVAL; + } + + irq_index = user_val - irq_first; + if (irq_index >= AB8500_NR_IRQS) + return -EINVAL; + + /* + * This will create a sysfs file named <irq-nr> which userspace can + * use to select or poll and get the AB8500 events + */ + dev_attr[irq_index] = kmalloc(sizeof(struct device_attribute), + GFP_KERNEL); + event_name[irq_index] = kmalloc(buf_size, GFP_KERNEL); + sprintf(event_name[irq_index], "%lu", user_val); + dev_attr[irq_index]->show = show_irq; + dev_attr[irq_index]->store = NULL; + dev_attr[irq_index]->attr.name = event_name[irq_index]; + dev_attr[irq_index]->attr.mode = S_IRUGO; + err = sysfs_create_file(&dev->kobj, &dev_attr[irq_index]->attr); + if (err < 0) { + printk(KERN_ERR "sysfs_create_file failed %d\n", err); + return err; + } + + err = request_threaded_irq(user_val, NULL, ab8500_debug_handler, + IRQF_SHARED | IRQF_NO_SUSPEND, "ab8500-debug", &dev->kobj); + if (err < 0) { + printk(KERN_ERR "request_threaded_irq failed %d, %lu\n", + err, user_val); + sysfs_remove_file(&dev->kobj, &dev_attr[irq_index]->attr); + return err; + } + + return buf_size; +} + +static ssize_t ab8500_unsubscribe_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + unsigned int irq_index; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + if (user_val < irq_first) { + dev_err(dev, "debugfs error input < %d\n", irq_first); + return -EINVAL; + } + if (user_val > irq_last) { + dev_err(dev, "debugfs error input > %d\n", irq_last); + return -EINVAL; + } + + irq_index = user_val - irq_first; + if (irq_index >= AB8500_NR_IRQS) + return -EINVAL; + + /* Set irq count to 0 when unsubscribe */ + irq_count[irq_index] = 0; + + if (dev_attr[irq_index]) + sysfs_remove_file(&dev->kobj, &dev_attr[irq_index]->attr); + + + free_irq(user_val, &dev->kobj); + kfree(event_name[irq_index]); + kfree(dev_attr[irq_index]); return count; } +/* + * - several deubgfs nodes fops + */ + static const struct file_operations ab8500_bank_fops = { .open = ab8500_bank_open, .write = ab8500_bank_write, @@ -545,65 +1409,177 @@ static const struct file_operations ab8500_val_fops = { .owner = THIS_MODULE, }; +static const struct file_operations ab8500_subscribe_fops = { + .open = ab8500_subscribe_unsubscribe_open, + .write = ab8500_subscribe_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab8500_unsubscribe_fops = { + .open = ab8500_subscribe_unsubscribe_open, + .write = ab8500_unsubscribe_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab8500_hwreg_fops = { + .open = ab8500_hwreg_open, + .write = ab8500_hwreg_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + static struct dentry *ab8500_dir; -static struct dentry *ab8500_reg_file; -static struct dentry *ab8500_bank_file; -static struct dentry *ab8500_address_file; -static struct dentry *ab8500_val_file; +static struct dentry *ab8500_gpadc_dir; static int __devinit ab8500_debug_probe(struct platform_device *plf) { + struct dentry *file; debug_bank = AB8500_MISC; debug_address = AB8500_REV_REG & 0x00FF; + irq_first = platform_get_irq_byname(plf, "IRQ_FIRST"); + if (irq_first < 0) { + dev_err(&plf->dev, "First irq not found, err %d\n", + irq_first); + return irq_first; + } + + irq_last = platform_get_irq_byname(plf, "IRQ_LAST"); + if (irq_last < 0) { + dev_err(&plf->dev, "Last irq not found, err %d\n", + irq_last); + return irq_last; + } + ab8500_dir = debugfs_create_dir(AB8500_NAME_STRING, NULL); if (!ab8500_dir) - goto exit_no_debugfs; + goto err; + + ab8500_gpadc_dir = debugfs_create_dir(AB8500_ADC_NAME_STRING, + ab8500_dir); + if (!ab8500_gpadc_dir) + goto err; + + file = debugfs_create_file("all-bank-registers", S_IRUGO, + ab8500_dir, &plf->dev, &ab8500_registers_fops); + if (!file) + goto err; + + file = debugfs_create_file("register-bank", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_bank_fops); + if (!file) + goto err; + + file = debugfs_create_file("register-address", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_address_fops); + if (!file) + goto err; + + file = debugfs_create_file("register-value", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_val_fops); + if (!file) + goto err; + + file = debugfs_create_file("irq-subscribe", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_subscribe_fops); + if (!file) + goto err; - ab8500_reg_file = debugfs_create_file("all-bank-registers", - S_IRUGO, ab8500_dir, &plf->dev, &ab8500_registers_fops); - if (!ab8500_reg_file) - goto exit_destroy_dir; + file = debugfs_create_file("irq-unsubscribe", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_unsubscribe_fops); + if (!file) + goto err; - ab8500_bank_file = debugfs_create_file("register-bank", - (S_IRUGO | S_IWUSR), ab8500_dir, &plf->dev, &ab8500_bank_fops); - if (!ab8500_bank_file) - goto exit_destroy_reg; + file = debugfs_create_file("hwreg", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_hwreg_fops); + if (!file) + goto err; - ab8500_address_file = debugfs_create_file("register-address", - (S_IRUGO | S_IWUSR), ab8500_dir, &plf->dev, - &ab8500_address_fops); - if (!ab8500_address_file) - goto exit_destroy_bank; + file = debugfs_create_file("bat_ctrl", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bat_ctrl_fops); + if (!file) + goto err; - ab8500_val_file = debugfs_create_file("register-value", - (S_IRUGO | S_IWUSR), ab8500_dir, &plf->dev, &ab8500_val_fops); - if (!ab8500_val_file) - goto exit_destroy_address; + file = debugfs_create_file("btemp_ball", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_btemp_ball_fops); + if (!file) + goto err; + + file = debugfs_create_file("main_charger_v", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("acc_detect1", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect1_fops); + if (!file) + goto err; + + file = debugfs_create_file("acc_detect2", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect2_fops); + if (!file) + goto err; + + file = debugfs_create_file("adc_aux1", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux1_fops); + if (!file) + goto err; + + file = debugfs_create_file("adc_aux2", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux2_fops); + if (!file) + goto err; + + file = debugfs_create_file("main_bat_v", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_bat_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("vbus_v", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_vbus_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("main_charger_c", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_c_fops); + if (!file) + goto err; + + file = debugfs_create_file("usb_charger_c", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_usb_charger_c_fops); + if (!file) + goto err; + + file = debugfs_create_file("bk_bat_v", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bk_bat_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("die_temp", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_die_temp_fops); + if (!file) + goto err; return 0; -exit_destroy_address: - debugfs_remove(ab8500_address_file); -exit_destroy_bank: - debugfs_remove(ab8500_bank_file); -exit_destroy_reg: - debugfs_remove(ab8500_reg_file); -exit_destroy_dir: - debugfs_remove(ab8500_dir); -exit_no_debugfs: +err: + if (ab8500_dir) + debugfs_remove_recursive(ab8500_dir); dev_err(&plf->dev, "failed to create debugfs entries.\n"); return -ENOMEM; } static int __devexit ab8500_debug_remove(struct platform_device *plf) { - debugfs_remove(ab8500_val_file); - debugfs_remove(ab8500_address_file); - debugfs_remove(ab8500_bank_file); - debugfs_remove(ab8500_reg_file); - debugfs_remove(ab8500_dir); - + debugfs_remove_recursive(ab8500_dir); return 0; } diff --git a/drivers/mfd/ab8500-denc.c b/drivers/mfd/ab8500-denc.c new file mode 100644 index 00000000000..1bf61e41bcd --- /dev/null +++ b/drivers/mfd/ab8500-denc.c @@ -0,0 +1,538 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * ST-Ericsson AB8500 DENC base driver + * + * Author: Marcel Tunnissen <marcel.tuennissen@stericsson.com> + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/list.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/err.h> +#include <linux/uaccess.h> +#include <linux/mfd/ab8500.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/ab8500/denc-regs.h> +#include <linux/mfd/ab8500/denc.h> + +#define AB8500_NAME "ab8500" +#define AB8500_DENC_NAME "ab8500_denc" + +struct device_usage { + struct list_head list; + struct platform_device *pdev; + bool taken; +}; +static LIST_HEAD(device_list); + +/* To get rid of the extra bank parameter: */ +#define AB8500_REG_BANK_NR(__reg) ((0xff00 & (__reg)) >> 8) +static inline u8 ab8500_rreg(struct device *dev, u32 reg) +{ + u8 val; + if (abx500_get_register_interruptible(dev, AB8500_REG_BANK_NR(reg), + reg, &val) < 0) + return 0; + else + return val; +} + +static inline int ab8500_wreg(struct device *dev, u32 reg, u8 val) +{ + return abx500_set_register_interruptible(dev, AB8500_REG_BANK_NR(reg), + reg, val); +} + +/* Only use in the macro below: */ +static inline int _ab8500_wreg_fld(struct device *dev, u32 reg, u8 val, + u8 mask, u8 shift) +{ + int ret; + u8 org_val; + + ret = abx500_get_register_interruptible(dev, AB8500_REG_BANK_NR(reg), + reg, &org_val); + if (ret < 0) + return ret; + else + ab8500_wreg(dev, reg, + (org_val & ~mask) | ((val << shift) & mask)); + return 0; +} + +#define ab8500_wr_fld(__d, __reg, __fld, __val) \ + _ab8500_wreg_fld(__d, __reg, __val, __reg##_##__fld##_MASK, \ + __reg##_##__fld##_SHIFT) + +#define ab8500_set_fld(__cur_val, __reg, __fld, __val) \ + (((__cur_val) & ~__reg##_##__fld##_MASK) | \ + (((__val) << __reg##_##__fld##_SHIFT) & __reg##_##__fld##_MASK)) + +#define AB8500_DENC_TRACE(__pd) dev_dbg(&(__pd)->dev, "%s\n", __func__) + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_ab8500_denc_dir; +static struct dentry *debugfs_ab8500_dump_regs_file; +static void ab8500_denc_conf_ddr(struct platform_device *pdev); +static int debugfs_ab8500_open_file(struct inode *inode, struct file *file); +static ssize_t debugfs_ab8500_dump_regs(struct file *file, char __user *buf, + size_t count, loff_t *f_pos); + +static const struct file_operations debugfs_ab8500_dump_regs_fops = { + .owner = THIS_MODULE, + .open = debugfs_ab8500_open_file, + .read = debugfs_ab8500_dump_regs, +}; +#endif /* CONFIG_DEBUG_FS */ + +static int __devinit ab8500_denc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct ab8500_platform_data *ab8500_pdata = + dev_get_platdata(pdev->dev.parent); + struct ab8500_denc_platform_data *pdata; + struct device_usage *device_data; + + AB8500_DENC_TRACE(pdev); + + if (ab8500_pdata == NULL) { + dev_err(&pdev->dev, "AB8500 platform data missing\n"); + return -EINVAL; + } + + pdata = ab8500_pdata->denc; + if (pdata == NULL) { + dev_err(&pdev->dev, "Denc platform data missing\n"); + return -EINVAL; + } + + device_data = kzalloc(sizeof(struct device_usage), GFP_KERNEL); + if (!device_data) { + dev_err(&pdev->dev, "Failed to allocate device data\n"); + return -ENOMEM; + } + device_data->pdev = pdev; + list_add_tail(&device_data->list, &device_list); + +#ifdef CONFIG_DEBUG_FS + debugfs_ab8500_denc_dir = debugfs_create_dir(pdev->name, NULL); + debugfs_ab8500_dump_regs_file = debugfs_create_file( + "dumpregs", S_IRUGO, + debugfs_ab8500_denc_dir, &pdev->dev, + &debugfs_ab8500_dump_regs_fops + ); +#endif /* CONFIG_DEBUG_FS */ + return ret; +} + +static int __devexit ab8500_denc_remove(struct platform_device *pdev) +{ + struct list_head *element; + struct device_usage *device_data; + + AB8500_DENC_TRACE(pdev); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(debugfs_ab8500_dump_regs_file); + debugfs_remove(debugfs_ab8500_denc_dir); +#endif /* CONFIG_DEBUG_FS */ + + list_for_each(element, &device_list) { + device_data = list_entry(element, struct device_usage, list); + if (device_data->pdev == pdev) { + list_del(element); + kzfree(device_data); + } + } + + return 0; +} + +static struct platform_driver ab8500_denc_driver = { + .probe = ab8500_denc_probe, + .remove = ab8500_denc_remove, + .driver = { + .name = "ab8500-denc", + }, +}; + +static void setup_27mhz(struct platform_device *pdev, bool enable) +{ + u8 data = ab8500_rreg(&pdev->dev, AB8500_SYS_ULP_CLK_CONF); + + AB8500_DENC_TRACE(pdev); + /* TODO: check if this field needs to be set */ + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_PD_ENA, + true); + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_BUF_ENA, + enable); + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, TVOUT_CLK_INV, + false); + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, TVOUT_CLK_DE_IN, + false); + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_STRE, + 1); + ab8500_wreg(&pdev->dev, AB8500_SYS_ULP_CLK_CONF, data); + + data = ab8500_rreg(&pdev->dev, AB8500_SYS_CLK_CTRL); + data = ab8500_set_fld(data, AB8500_SYS_CLK_CTRL, TVOUT_CLK_VALID, + enable); + data = ab8500_set_fld(data, AB8500_SYS_CLK_CTRL, TVOUT_PLL_ENA, + enable); + ab8500_wreg(&pdev->dev, AB8500_SYS_CLK_CTRL, data); +} + +static u32 map_tv_std(enum ab8500_denc_TV_std std) +{ + switch (std) { + case TV_STD_PAL_BDGHI: + return AB8500_DENC_CONF0_STD_PAL_BDGHI; + case TV_STD_PAL_N: + return AB8500_DENC_CONF0_STD_PAL_N; + case TV_STD_PAL_M: + return AB8500_DENC_CONF0_STD_PAL_M; + case TV_STD_NTSC_M: + return AB8500_DENC_CONF0_STD_NTSC_M; + default: + return 0; + } +} + +static u32 map_cr_filter(enum ab8500_denc_cr_filter_bandwidth bw) +{ + switch (bw) { + case TV_CR_NTSC_LOW_DEF_FILTER: + return AB8500_DENC_CONF1_FLT_1_1MHZ; + case TV_CR_PAL_LOW_DEF_FILTER: + return AB8500_DENC_CONF1_FLT_1_3MHZ; + case TV_CR_NTSC_HIGH_DEF_FILTER: + return AB8500_DENC_CONF1_FLT_1_6MHZ; + case TV_CR_PAL_HIGH_DEF_FILTER: + return AB8500_DENC_CONF1_FLT_1_9MHZ; + default: + return 0; + } +} + +static u32 map_phase_rst_mode(enum ab8500_denc_phase_reset_mode mode) +{ + switch (mode) { + case TV_PHASE_RST_MOD_DISABLE: + return AB8500_DENC_CONF8_PH_RST_MODE_DISABLED; + case TV_PHASE_RST_MOD_FROM_PHASE_BUF: + return AB8500_DENC_CONF8_PH_RST_MODE_UPDATE_FROM_PHASE_BUF; + case TV_PHASE_RST_MOD_FROM_INC_DFS: + return AB8500_DENC_CONF8_PH_RST_MODE_UPDATE_FROM_INC_DFS; + case TV_PHASE_RST_MOD_RST: + return AB8500_DENC_CONF8_PH_RST_MODE_RESET; + default: + return 0; + } +} + +static u32 map_plug_time(enum ab8500_denc_plug_time time) +{ + switch (time) { + case TV_PLUG_TIME_0_5S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_0_5S; + case TV_PLUG_TIME_1S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_1S; + case TV_PLUG_TIME_1_5S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_1_5S; + case TV_PLUG_TIME_2S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_2S; + case TV_PLUG_TIME_2_5S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_2_5S; + case TV_PLUG_TIME_3S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_3S; + default: + return 0; + } +} + +struct platform_device *ab8500_denc_get_device(void) +{ + struct list_head *element; + struct device_usage *device_data; + + pr_debug("%s\n", __func__); + list_for_each(element, &device_list) { + device_data = list_entry(element, struct device_usage, list); + if (!device_data->taken) { + device_data->taken = true; + return device_data->pdev; + } + } + return NULL; +} +EXPORT_SYMBOL(ab8500_denc_get_device); + +void ab8500_denc_put_device(struct platform_device *pdev) +{ + struct list_head *element; + struct device_usage *device_data; + + AB8500_DENC_TRACE(pdev); + list_for_each(element, &device_list) { + device_data = list_entry(element, struct device_usage, list); + if (device_data->pdev == pdev) + device_data->taken = false; + } +} +EXPORT_SYMBOL(ab8500_denc_put_device); + +void ab8500_denc_reset(struct platform_device *pdev, bool hard) +{ + AB8500_DENC_TRACE(pdev); + if (hard) { + u8 data = ab8500_rreg(&pdev->dev, AB8500_CTRL3); + /* reset start */ + ab8500_wreg(&pdev->dev, AB8500_CTRL3, + ab8500_set_fld(data, AB8500_CTRL3, RESET_DENC_N, 0) + ); + /* reset done */ + ab8500_wreg(&pdev->dev, AB8500_CTRL3, + ab8500_set_fld(data, AB8500_CTRL3, RESET_DENC_N, 1) + ); + } else { + ab8500_wr_fld(&pdev->dev, AB8500_DENC_CONF6, SOFT_RESET, 1); + mdelay(10); + } +} +EXPORT_SYMBOL(ab8500_denc_reset); + +void ab8500_denc_power_up(struct platform_device *pdev) +{ + setup_27mhz(pdev, true); +} +EXPORT_SYMBOL(ab8500_denc_power_up); + +void ab8500_denc_power_down(struct platform_device *pdev) +{ + setup_27mhz(pdev, false); +} +EXPORT_SYMBOL(ab8500_denc_power_down); + +void ab8500_denc_conf(struct platform_device *pdev, + struct ab8500_denc_conf *conf) +{ + u8 data; + + AB8500_DENC_TRACE(pdev); + + ab8500_wreg(&pdev->dev, AB8500_DENC_CONF0, + AB8500_VAL2REG(AB8500_DENC_CONF0, STD, map_tv_std(conf->TV_std)) + | + AB8500_VAL2REG(AB8500_DENC_CONF0, SYNC, + conf->test_pattern ? AB8500_DENC_CONF0_SYNC_AUTO_TEST : + AB8500_DENC_CONF0_SYNC_F_BASED_SLAVE + ) + ); + ab8500_wreg(&pdev->dev, AB8500_DENC_CONF1, + AB8500_VAL2REG(AB8500_DENC_CONF1, BLK_LI, + !conf->partial_blanking) + | + AB8500_VAL2REG(AB8500_DENC_CONF1, FLT, + map_cr_filter(conf->cr_filter)) + | + AB8500_VAL2REG(AB8500_DENC_CONF1, CO_KI, conf->suppress_col) + | + AB8500_VAL2REG(AB8500_DENC_CONF1, SETUP_MAIN, + conf->black_level_setup) + /* TODO: handle cc field: set to 0 now */ + ); + + data = ab8500_rreg(&pdev->dev, AB8500_DENC_CONF2); + data = ab8500_set_fld(data, AB8500_DENC_CONF2, N_INTRL, + conf->progressive); + ab8500_wreg(&pdev->dev, AB8500_DENC_CONF2, data); + + ab8500_wreg(&pdev->dev, AB8500_DENC_CONF8, + AB8500_VAL2REG(AB8500_DENC_CONF8, PH_RST_MODE, + map_phase_rst_mode(conf->phase_reset_mode)) + | + AB8500_VAL2REG(AB8500_DENC_CONF8, VAL_422_MUX, + conf->act_output) + | + AB8500_VAL2REG(AB8500_DENC_CONF8, BLK_ALL, + conf->blank_all) + ); + data = ab8500_rreg(&pdev->dev, AB8500_TVOUT_CTRL); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, DAC_CTRL0, + conf->dac_enable); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, DAC_CTRL1, + conf->act_dc_output); + ab8500_wreg(&pdev->dev, AB8500_TVOUT_CTRL, data); + + /* no support for DDR in early versions */ + if (AB8500_REG2VAL(AB8500_REV, FULL_MASK, + ab8500_rreg(&pdev->dev, AB8500_REV)) > 0) + ab8500_denc_conf_ddr(pdev); +} +EXPORT_SYMBOL(ab8500_denc_conf); + +void ab8500_denc_conf_plug_detect(struct platform_device *pdev, + bool enable, bool load_RC, + enum ab8500_denc_plug_time time) +{ + u8 data; + + AB8500_DENC_TRACE(pdev); + data = ab8500_rreg(&pdev->dev, AB8500_TVOUT_CTRL); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, TV_PLUG_ON, enable); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, TV_LOAD_RC, load_RC); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, PLUG_TV_TIME, + map_plug_time(time)); + ab8500_wreg(&pdev->dev, AB8500_TVOUT_CTRL, data); +} +EXPORT_SYMBOL(ab8500_denc_conf_plug_detect); + +void ab8500_denc_mask_int_plug_det(struct platform_device *pdev, bool plug, + bool unplug) +{ + u8 data = ab8500_rreg(&pdev->dev, AB8500_IT_MASK1); + + AB8500_DENC_TRACE(pdev); + data = ab8500_set_fld(data, AB8500_IT_MASK1, PLUG_TV_DET, plug); + data = ab8500_set_fld(data, AB8500_IT_MASK1, UNPLUG_TV_DET, unplug); + ab8500_wreg(&pdev->dev, AB8500_IT_MASK1, data); +} +EXPORT_SYMBOL(ab8500_denc_mask_int_plug_det); + +static void ab8500_denc_conf_ddr(struct platform_device *pdev) +{ + struct ab8500_platform_data *core_pdata; + struct ab8500_denc_platform_data *denc_pdata; + + AB8500_DENC_TRACE(pdev); + core_pdata = dev_get_platdata(pdev->dev.parent); + denc_pdata = core_pdata->denc; + ab8500_wreg(&pdev->dev, AB8500_TVOUT_CTRL2, + AB8500_VAL2REG(AB8500_TVOUT_CTRL2, + DENC_DDR, denc_pdata->ddr_enable) | + AB8500_VAL2REG(AB8500_TVOUT_CTRL2, SWAP_DDR_DATA_IN, + denc_pdata->ddr_little_endian)); +} + +#ifdef CONFIG_DEBUG_FS +static int debugfs_ab8500_open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +#define DEBUG_BUF_SIZE 900 + +#define AB8500_GPIO_DIR5 0x1014 +#define AB8500_GPIO_DIR5_35_SHIFT 2 +#define AB8500_GPIO_DIR5_35_MASK (1 << AB8500_GPIO_DIR5_35_SHIFT) +#define AB8500_GPIO_OUT5 0x1024 +#define AB8500_GPIO_OUT5_35_SHIFT 2 +#define AB8500_GPIO_OUT5_35_MASK (1 << AB8500_GPIO_OUT5_35_SHIFT) +#define AB8500_GPIO_OUT5_35_VIDEO 0 +#define AB8500_GPIO_OUT5_35_AUDIO 1 +#define AB8500_GPIO_NPUD5 0x1034 +#define AB8500_GPIO_NPUD5_35_SHIFT 2 +#define AB8500_GPIO_NPUD5_35_MASK (1 << AB8500_GPIO_NPUD5_35_SHIFT) +#define AB8500_GPIO_NPUD5_35_ACTIVE 0 +#define AB8500_GPIO_NPUD5_35_INACTIVE 1 + +static ssize_t debugfs_ab8500_dump_regs(struct file *file, char __user *buf, + size_t count, loff_t *f_pos) +{ + int ret = 0; + size_t data_size = 0; + char buffer[DEBUG_BUF_SIZE]; + struct device *dev = file->private_data; + + data_size += sprintf(buffer + data_size, + "AB8500 DENC registers:\n" + "------Regulators etc ----------\n" + "CTRL3 : 0x%04x = 0x%02x\n" + "SYSULPCLK_CONF: 0x%04x = 0x%02x\n" + "SYSCLK_CTRL : 0x%04x = 0x%02x\n" + "REGU_MISC1 : 0x%04x = 0x%02x\n" + "VAUX12_REGU : 0x%04x = 0x%02x\n" + "VAUX1_SEL1 : 0x%04x = 0x%02x\n" + "------TVout only --------------\n" + "DENC_CONF0 : 0x%04x = 0x%02x\n" + "DENC_CONF1 : 0x%04x = 0x%02x\n" + "DENC_CONF2 : 0x%04x = 0x%02x\n" + "DENC_CONF6 : 0x%04x = 0x%02x\n" + "DENC_CONF8 : 0x%04x = 0x%02x\n" + "TVOUT_CTRL : 0x%04x = 0x%02x\n" + "TVOUT_CTRL2 : 0x%04x = 0x%02x\n" + "IT_MASK1 : 0x%04x = 0x%02x\n" + "------AV connector-------------\n" + "GPIO_DIR5 : 0x%04x = 0x%02x\n" + "GPIO_OUT5 : 0x%04x = 0x%02x\n" + "GPIO_NPUD5 : 0x%04x = 0x%02x\n" + , + AB8500_CTRL3, ab8500_rreg(dev, AB8500_CTRL3), + AB8500_SYS_ULP_CLK_CONF, ab8500_rreg(dev, + AB8500_SYS_ULP_CLK_CONF), + AB8500_SYS_CLK_CTRL, ab8500_rreg(dev, AB8500_SYS_CLK_CTRL), + AB8500_REGU_MISC1, ab8500_rreg(dev, AB8500_REGU_MISC1), + AB8500_VAUX12_REGU, ab8500_rreg(dev, AB8500_VAUX12_REGU), + AB8500_VAUX1_SEL, ab8500_rreg(dev, AB8500_VAUX1_SEL), + AB8500_DENC_CONF0, ab8500_rreg(dev, AB8500_DENC_CONF0), + AB8500_DENC_CONF1, ab8500_rreg(dev, AB8500_DENC_CONF1), + AB8500_DENC_CONF2, ab8500_rreg(dev, AB8500_DENC_CONF2), + AB8500_DENC_CONF6, ab8500_rreg(dev, AB8500_DENC_CONF6), + AB8500_DENC_CONF8, ab8500_rreg(dev, AB8500_DENC_CONF8), + AB8500_TVOUT_CTRL, ab8500_rreg(dev, AB8500_TVOUT_CTRL), + AB8500_TVOUT_CTRL2, ab8500_rreg(dev, AB8500_TVOUT_CTRL2), + AB8500_IT_MASK1, ab8500_rreg(dev, AB8500_IT_MASK1), + AB8500_GPIO_DIR5, ab8500_rreg(dev, AB8500_GPIO_DIR5), + AB8500_GPIO_OUT5, ab8500_rreg(dev, AB8500_GPIO_OUT5), + AB8500_GPIO_NPUD5, ab8500_rreg(dev, AB8500_GPIO_NPUD5) + ); + if (data_size >= DEBUG_BUF_SIZE) { + printk(KERN_EMERG "AB8500 DENC: Buffer overrun\n"); + ret = -EINVAL; + goto out; + } + + /* check if read done */ + if (*f_pos > data_size) + goto out; + + if (*f_pos + count > data_size) + count = data_size - *f_pos; + + if (copy_to_user(buf, buffer + *f_pos, count)) + ret = -EINVAL; + *f_pos += count; + ret = count; +out: + return ret; +} +#endif /* CONFIG_DEBUG_FS */ + +/* Module init */ +static int __init ab8500_denc_init(void) +{ + return platform_driver_register(&ab8500_denc_driver); +} +module_init(ab8500_denc_init); + +static void __exit ab8500_denc_exit(void) +{ + platform_driver_unregister(&ab8500_denc_driver); +} +module_exit(ab8500_denc_exit); + +MODULE_AUTHOR("Marcel Tunnissen <marcel.tuennissen@stericsson.com>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ST-Ericsson AB8500 DENC driver"); diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index f16afb234ff..0794abb86a6 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -130,25 +130,24 @@ static LIST_HEAD(ab8500_gpadc_list); * ab8500_gpadc_get() - returns a reference to the primary AB8500 GPADC * (i.e. the first GPADC in the instance list) */ -struct ab8500_gpadc *ab8500_gpadc_get(char *name) +struct ab8500_gpadc *ab8500_gpadc_get(void) { struct ab8500_gpadc *gpadc; + gpadc = list_first_entry(&ab8500_gpadc_list, struct ab8500_gpadc, node); - list_for_each_entry(gpadc, &ab8500_gpadc_list, node) { - if (!strcmp(name, dev_name(gpadc->dev))) - return gpadc; - } - - return ERR_PTR(-ENOENT); + return gpadc; } EXPORT_SYMBOL(ab8500_gpadc_get); -static int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 input, +/** + * ab8500_gpadc_ad_to_voltage() - Convert a raw ADC value to a voltage + */ +int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel, int ad_value) { int res; - switch (input) { + switch (channel) { case MAIN_CHARGER_V: /* For some reason we don't have calibrated data */ if (!gpadc->cal_data[ADC_INPUT_VMAIN].gain) { @@ -235,15 +234,41 @@ static int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 input, /** * ab8500_gpadc_convert() - gpadc conversion - * @input: analog input to be converted to digital data + * @channel: analog channel to be converted to digital data * * This function converts the selected analog i/p to digital * data. */ -int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) +int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 channel) +{ + int ad_value; + int voltage; + + ad_value = ab8500_gpadc_read_raw(gpadc, channel); + if (ad_value < 0) { + dev_err(gpadc->dev, "GPADC raw value failed ch: %d\n", channel); + return ad_value; + } + + voltage = ab8500_gpadc_ad_to_voltage(gpadc, channel, ad_value); + + if (voltage < 0) + dev_err(gpadc->dev, "GPADC to voltage conversion failed ch:" + " %d AD: 0x%x\n", channel, ad_value); + + return voltage; +} + +/** + * ab8500_gpadc_read_raw() - gpadc read + * @channel: analog channel to be read + * + * This function obtains the raw ADC value, this then needs + * to be converted by calling ab8500_gpadc_ad_to_voltage() + */ +int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) { int ret; - u16 data = 0; int looplimit = 0; u8 val, low_data, high_data; @@ -278,9 +303,9 @@ int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) goto out; } - /* Select the input source and set average samples to 16 */ + /* Select the channel source and set average samples to 16 */ ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, - AB8500_GPADC_CTRL2_REG, (input | SW_AVG_16)); + AB8500_GPADC_CTRL2_REG, (channel | SW_AVG_16)); if (ret < 0) { dev_err(gpadc->dev, "gpadc_conversion: set avg samples failed\n"); @@ -292,7 +317,7 @@ int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) * charging current sense if it needed, ABB 3.0 needs some special * treatment too. */ - switch (input) { + switch (channel) { case MAIN_CHARGER_C: case USB_CHARGER_C: ret = abx500_mask_and_set_register_interruptible(gpadc->dev, @@ -313,7 +338,7 @@ int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) * Delay might be needed for ABB8500 cut 3.0, if not, remove * when hardware will be availible */ - msleep(1); + mdelay(1); break; } /* Intentional fallthrough */ @@ -359,7 +384,6 @@ int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) goto out; } - data = (high_data << 8) | low_data; /* Disable GPADC */ ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_CTRL1_REG, DIS_GPADC); @@ -370,8 +394,8 @@ int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) /* Disable VTVout LDO this is required for GPADC */ regulator_disable(gpadc->regu); mutex_unlock(&gpadc->ab8500_gpadc_lock); - ret = ab8500_gpadc_ad_to_voltage(gpadc, input, data); - return ret; + + return (high_data << 8) | low_data; out: /* @@ -385,10 +409,9 @@ out: regulator_disable(gpadc->regu); mutex_unlock(&gpadc->ab8500_gpadc_lock); dev_err(gpadc->dev, - "gpadc_conversion: Failed to AD convert channel %d\n", input); + "gpadc_conversion: Failed to AD convert channel %d\n", channel); return ret; } -EXPORT_SYMBOL(ab8500_gpadc_convert); /** * ab8500_bm_gpswadcconvend_handler() - isr for s/w gpadc conversion completion diff --git a/drivers/mfd/ab8500-i2c.c b/drivers/mfd/ab8500-i2c.c index 9be541c6b00..3a94ec80192 100644 --- a/drivers/mfd/ab8500-i2c.c +++ b/drivers/mfd/ab8500-i2c.c @@ -11,7 +11,7 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/mfd/ab8500.h> -#include <linux/mfd/db8500-prcmu.h> +#include <linux/mfd/dbx500-prcmu.h> static int ab8500_i2c_write(struct ab8500 *ab8500, u16 addr, u8 data) { diff --git a/drivers/mfd/ab8500-sysctrl.c b/drivers/mfd/ab8500-sysctrl.c index 392185965b3..b22c6bfc9fd 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -6,12 +6,114 @@ #include <linux/err.h> #include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/reboot.h> +#include <linux/signal.h> +#include <linux/power_supply.h> #include <linux/mfd/ab8500.h> #include <linux/mfd/abx500.h> #include <linux/mfd/ab8500/sysctrl.h> +#include <linux/time.h> +#include <linux/hwmon.h> static struct device *sysctrl_dev; +void ab8500_power_off(void) +{ + struct ab8500_platform_data *plat; + struct timespec ts; + sigset_t old; + sigset_t all; + static char *pss[] = {"ab8500_ac", "ab8500_usb"}; + int i; + bool charger_present = false; + union power_supply_propval val; + struct power_supply *psy; + int ret; + + /* + * If we have a charger connected and we're powering off, + * reboot into charge-only mode. + */ + + for (i = 0; i < ARRAY_SIZE(pss); i++) { + psy = power_supply_get_by_name(pss[i]); + if (!psy) + continue; + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &val); + + if (!ret && val.intval) { + charger_present = true; + break; + } + } + + if (!charger_present) + goto shutdown; + + /* Check if battery is known */ + psy = power_supply_get_by_name("ab8500_btemp"); + if (psy) { + ret = psy->get_property(psy, POWER_SUPPLY_PROP_TECHNOLOGY, + &val); + if (!ret && val.intval != POWER_SUPPLY_TECHNOLOGY_UNKNOWN) { + printk(KERN_INFO + "Charger \"%s\" is connected with known battery." + " Rebooting.\n", + pss[i]); + machine_restart("charging"); + } + } + +shutdown: + sigfillset(&all); + + plat = dev_get_platdata(sysctrl_dev->parent); + getnstimeofday(&ts); + if (!sigprocmask(SIG_BLOCK, &all, &old)) { + if (ts.tv_sec == 0 || + (ts.tv_sec - plat->thermal_set_time_sec > + plat->thermal_time_out)) + plat->thermal_power_off_pending = false; + if (!plat->thermal_power_off_pending) { + (void)ab8500_sysctrl_set(AB8500_STW4500CTRL1, + AB8500_STW4500CTRL1_SWOFF | + AB8500_STW4500CTRL1_SWRESET4500N); + (void)sigprocmask(SIG_SETMASK, &old, NULL); + } else { + (void)ab8500_sysctrl_set(AB8500_STW4500CTRL1, + AB8500_STW4500CTRL1_THDB8500SWOFF | + AB8500_STW4500CTRL1_SWRESET4500N); + (void)sigprocmask(SIG_SETMASK, &old, NULL); + } + } +} + +static int ab8500_notifier_call(struct notifier_block *this, + unsigned long val, void *data) +{ + struct ab8500_platform_data *plat; + static struct timespec ts; + if (sysctrl_dev == NULL) + return -EAGAIN; + + plat = dev_get_platdata(sysctrl_dev->parent); + if (val) { + getnstimeofday(&ts); + plat->thermal_set_time_sec = ts.tv_sec; + plat->thermal_power_off_pending = true; + } else { + plat->thermal_set_time_sec = 0; + plat->thermal_power_off_pending = false; + } + return 0; +} + +static struct notifier_block ab8500_notifier = { + .notifier_call = ab8500_notifier_call, +}; + static inline bool valid_bank(u8 bank) { return ((bank == AB8500_SYS_CTRL1_BLOCK) || @@ -50,7 +152,13 @@ int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value) static int __devinit ab8500_sysctrl_probe(struct platform_device *pdev) { + struct ab8500_platform_data *plat; + sysctrl_dev = &pdev->dev; + plat = dev_get_platdata(pdev->dev.parent); + if (plat->pm_power_off) + pm_power_off = ab8500_power_off; + hwmon_notifier_register(&ab8500_notifier); return 0; } diff --git a/drivers/mfd/db5500-prcmu-regs.h b/drivers/mfd/db5500-prcmu-regs.h index 9a8e9e4ddd3..e8aa2901478 100644 --- a/drivers/mfd/db5500-prcmu-regs.h +++ b/drivers/mfd/db5500-prcmu-regs.h @@ -1,115 +1,126 @@ /* - * Copyright (C) STMicroelectronics 2009 - * Copyright (C) ST-Ericsson SA 2010 - * - * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> - * Author: Sundar Iyer <sundar.iyer@stericsson.com> + * Copyright (C) ST-Ericsson SA 2011 * * License Terms: GNU General Public License v2 - * - * PRCM Unit registers */ -#ifndef __MACH_PRCMU_REGS_H -#define __MACH_PRCMU_REGS_H - -#include <mach/hardware.h> - -#define PRCM_ARM_PLLDIVPS (_PRCMU_BASE + 0x118) -#define PRCM_ARM_PLLDIVPS_ARM_BRM_RATE 0x3f -#define PRCM_ARM_PLLDIVPS_MAX_MASK 0xf - -#define PRCM_PLLARM_LOCKP (_PRCMU_BASE + 0x0a8) -#define PRCM_PLLARM_LOCKP_PRCM_PLLARM_LOCKP3 0x2 - -#define PRCM_ARM_CHGCLKREQ (_PRCMU_BASE + 0x114) -#define PRCM_ARM_CHGCLKREQ_PRCM_ARM_CHGCLKREQ 0x1 - -#define PRCM_PLLARM_ENABLE (_PRCMU_BASE + 0x98) -#define PRCM_PLLARM_ENABLE_PRCM_PLLARM_ENABLE 0x1 -#define PRCM_PLLARM_ENABLE_PRCM_PLLARM_COUNTON 0x100 - -#define PRCM_ARMCLKFIX_MGT (_PRCMU_BASE + 0x0) -#define PRCM_A9_RESETN_CLR (_PRCMU_BASE + 0x1f4) -#define PRCM_A9_RESETN_SET (_PRCMU_BASE + 0x1f0) -#define PRCM_ARM_LS_CLAMP (_PRCMU_BASE + 0x30c) -#define PRCM_SRAM_A9 (_PRCMU_BASE + 0x308) - -/* ARM WFI Standby signal register */ -#define PRCM_ARM_WFI_STANDBY (_PRCMU_BASE + 0x130) -#define PRCM_IOCR (_PRCMU_BASE + 0x310) -#define PRCM_IOCR_IOFORCE 0x1 +#ifndef __MACH_PRCMU_REGS_DB5500_H +#define __MACH_PRCMU_REGS_DB5500_H + +#define BITS(_start, _end) ((BIT(_end) - BIT(_start)) + BIT(_end)) + +#define PRCM_TCR 0x1C8 +#define PRCM_TCR_TENSEL_MASK BITS(0, 7) +#define PRCM_TCR_STOP_TIMERS BIT(16) +#define PRCM_TCR_DOZE_MODE BIT(17) + +/* PRCMU HW semaphore */ +#define PRCM_SEM 0x400 +#define PRCM_SEM_PRCM_SEM BIT(0) + +#define DB5500_PRCM_ACLK_MGT 0x004 +#define DB5500_PRCM_SVACLK_MGT 0x008 +#define DB5500_PRCM_SIACLK_MGT 0x00C +#define DB5500_PRCM_SGACLK_MGT 0x014 +#define DB5500_PRCM_UARTCLK_MGT 0x018 +#define DB5500_PRCM_MSP02CLK_MGT 0x01C +#define DB5500_PRCM_I2CCLK_MGT 0x020 +#define DB5500_PRCM_SDMMCCLK_MGT 0x024 +#define DB5500_PRCM_PER1CLK_MGT 0x02C +#define DB5500_PRCM_PER2CLK_MGT 0x030 +#define DB5500_PRCM_PER3CLK_MGT 0x034 +#define DB5500_PRCM_PER5CLK_MGT 0x038 +#define DB5500_PRCM_PER6CLK_MGT 0x03C +#define DB5500_PRCM_IRDACLK_MGT 0x040 +#define DB5500_PRCM_PWMCLK_MGT 0x044 +#define DB5500_PRCM_SPARE1CLK_MGT 0x048 +#define DB5500_PRCM_IRRCCLK_MGT 0x04C +#define DB5500_PRCM_HDMICLK_MGT 0x058 +#define DB5500_PRCM_APEATCLK_MGT 0x05C +#define DB5500_PRCM_APETRACECLK_MGT 0x060 +#define DB5500_PRCM_MCDECLK_MGT 0x064 +#define DB5500_PRCM_DSIALTCLK_MGT 0x06C +#define DB5500_PRCM_DMACLK_MGT 0x074 +#define DB5500_PRCM_B2R2CLK_MGT 0x078 +#define DB5500_PRCM_TVCLK_MGT 0x07C +#define DB5500_PRCM_RNGCLK_MGT 0x284 + +#define PRCM_CLK_MGT_CLKPLLDIV_MASK BITS(0, 4) +#define PRCM_CLK_MGT_CLKPLLDIV_SHIFT 0 +#define PRCM_CLK_MGT_CLKPLLSW_MASK BITS(5, 7) +#define PRCM_CLK_MGT_CLKEN BIT(8) + +#define PRCM_ARM_IT1_CLEAR 0x48C +#define PRCM_ARM_IT1_VAL 0x494 /* CPU mailbox registers */ -#define PRCM_MBOX_CPU_VAL (_PRCMU_BASE + 0x0fc) -#define PRCM_MBOX_CPU_SET (_PRCMU_BASE + 0x100) -#define PRCM_MBOX_CPU_CLR (_PRCMU_BASE + 0x104) - -/* Dual A9 core interrupt management unit registers */ -#define PRCM_A9_MASK_REQ (_PRCMU_BASE + 0x328) -#define PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ 0x1 - -#define PRCM_A9_MASK_ACK (_PRCMU_BASE + 0x32c) -#define PRCM_ARMITMSK31TO0 (_PRCMU_BASE + 0x11c) -#define PRCM_ARMITMSK63TO32 (_PRCMU_BASE + 0x120) -#define PRCM_ARMITMSK95TO64 (_PRCMU_BASE + 0x124) -#define PRCM_ARMITMSK127TO96 (_PRCMU_BASE + 0x128) -#define PRCM_POWER_STATE_VAL (_PRCMU_BASE + 0x25C) -#define PRCM_ARMITVAL31TO0 (_PRCMU_BASE + 0x260) -#define PRCM_ARMITVAL63TO32 (_PRCMU_BASE + 0x264) -#define PRCM_ARMITVAL95TO64 (_PRCMU_BASE + 0x268) -#define PRCM_ARMITVAL127TO96 (_PRCMU_BASE + 0x26C) - -#define PRCM_HOSTACCESS_REQ (_PRCMU_BASE + 0x334) -#define ARM_WAKEUP_MODEM 0x1 - -#define PRCM_ARM_IT1_CLEAR (_PRCMU_BASE + 0x48C) -#define PRCM_ARM_IT1_VAL (_PRCMU_BASE + 0x494) -#define PRCM_HOLD_EVT (_PRCMU_BASE + 0x174) - -#define PRCM_ITSTATUS0 (_PRCMU_BASE + 0x148) -#define PRCM_ITSTATUS1 (_PRCMU_BASE + 0x150) -#define PRCM_ITSTATUS2 (_PRCMU_BASE + 0x158) -#define PRCM_ITSTATUS3 (_PRCMU_BASE + 0x160) -#define PRCM_ITSTATUS4 (_PRCMU_BASE + 0x168) -#define PRCM_ITSTATUS5 (_PRCMU_BASE + 0x484) -#define PRCM_ITCLEAR5 (_PRCMU_BASE + 0x488) -#define PRCM_ARMIT_MASKXP70_IT (_PRCMU_BASE + 0x1018) +#define PRCM_MBOX_CPU_VAL 0x0FC +#define PRCM_MBOX_CPU_SET 0x100 /* System reset register */ -#define PRCM_APE_SOFTRST (_PRCMU_BASE + 0x228) - -/* Level shifter and clamp control registers */ -#define PRCM_MMIP_LS_CLAMP_SET (_PRCMU_BASE + 0x420) -#define PRCM_MMIP_LS_CLAMP_CLR (_PRCMU_BASE + 0x424) +#define PRCM_APE_SOFTRST 0x228 /* PRCMU clock/PLL/reset registers */ -#define PRCM_PLLDSI_FREQ (_PRCMU_BASE + 0x500) -#define PRCM_PLLDSI_ENABLE (_PRCMU_BASE + 0x504) -#define PRCM_PLLDSI_LOCKP (_PRCMU_BASE + 0x508) -#define PRCM_LCDCLK_MGT (_PRCMU_BASE + 0x044) -#define PRCM_MCDECLK_MGT (_PRCMU_BASE + 0x064) -#define PRCM_HDMICLK_MGT (_PRCMU_BASE + 0x058) -#define PRCM_TVCLK_MGT (_PRCMU_BASE + 0x07c) -#define PRCM_DSI_PLLOUT_SEL (_PRCMU_BASE + 0x530) -#define PRCM_DSITVCLK_DIV (_PRCMU_BASE + 0x52C) -#define PRCM_PLLDSI_LOCKP (_PRCMU_BASE + 0x508) -#define PRCM_APE_RESETN_SET (_PRCMU_BASE + 0x1E4) -#define PRCM_APE_RESETN_CLR (_PRCMU_BASE + 0x1E8) -#define PRCM_CLKOCR (_PRCMU_BASE + 0x1CC) - -/* ePOD and memory power signal control registers */ -#define PRCM_EPOD_C_SET (_PRCMU_BASE + 0x410) -#define PRCM_SRAM_LS_SLEEP (_PRCMU_BASE + 0x304) - -/* Debug power control unit registers */ -#define PRCM_POWER_STATE_SET (_PRCMU_BASE + 0x254) +#define PRCM_PLLDSI_FREQ 0x500 +#define PRCM_PLLDSI_ENABLE 0x504 +#define PRCM_PLLDSI_LOCKP 0x508 +#define PRCM_DSI_PLLOUT_SEL 0x530 +#define PRCM_DSITVCLK_DIV 0x52C +#define PRCM_APE_RESETN_SET 0x1E4 +#define PRCM_APE_RESETN_CLR 0x1E8 + +/* CLKOUTx SEL0 settings */ +#define CLKOUT_SEL0_REF_CLK 0x01 /* 0b 0001 */ +#define CLKOUT_SEL0_RTC_CLK0 0x02 /* 0b 0010 */ +#define CLKOUT_SEL0_ULP_CLK 0x04 /* 0b 0100 */ +#define CLKOUT_SEL0_SEL_CLK 0x08 /* 0b 1000 */ + +/* CLKOUTx SEL settings */ +#define CLKOUT_SEL_STATIC0 0x0001 /* 0b 00 0000 0001 */ +#define CLKOUT_SEL_REFCLK 0x0002 /* 0b 00 0000 0010 */ +#define CLKOUT_SEL_ULPCLK 0x0004 /* 0b 00 0000 0100 */ +#define CLKOUT_SEL_ARMCLK 0x0008 /* 0b 00 0000 1000 */ +#define CLKOUT_SEL_SYSACC0CLK 0x0010 /* 0b 00 0001 0000 */ +#define CLKOUT_SEL_SOC0PLLCLK 0x0020 /* 0b 00 0010 0000 */ +#define CLKOUT_SEL_SOC1PLLCLK 0x0040 /* 0b 00 0100 0000 */ +#define CLKOUT_SEL_DDRPLLCLK 0x0080 /* 0b 00 1000 0000 */ +#define CLKOUT_SEL_TVCLK 0x0100 /* 0b 01 0000 0000 */ +#define CLKOUT_SEL_IRDACLK 0x0200 /* 0b 10 0000 0000 */ + +/* CLKOUTx dividers */ +#define CLKOUT_DIV_2 0x00 /* 0b 000 */ +#define CLKOUT_DIV_4 0x01 /* 0b 001 */ +#define CLKOUT_DIV_8 0x02 /* 0b 010 */ +#define CLKOUT_DIV_16 0x03 /* 0b 011 */ +#define CLKOUT_DIV_32 0x04 /* 0b 100 */ +#define CLKOUT_DIV_64 0x05 /* 0b 101 */ +/* Values 0x06 and 0x07 will also set the CLKOUTx divider to 64. */ + +/* PRCM_CLKOCR CLKOUTx Control registers */ +#define PRCM_CLKOCR 0x1CC +#define PRCM_CLKOCR_CLKOUT0_SEL0_SHIFT 0 +#define PRCM_CLKOCR_CLKOUT0_SEL0_MASK BITS(0, 3) +#define PRCM_CLKOCR_CLKOUT0_SEL_SHIFT 4 +#define PRCM_CLKOCR_CLKOUT0_SEL_MASK BITS(4, 13) +#define PRCM_CLKOCR_CLKOUT1_SEL0_SHIFT 16 +#define PRCM_CLKOCR_CLKOUT1_SEL0_MASK BITS(16, 19) +#define PRCM_CLKOCR_CLKOUT1_SEL_SHIFT 20 +#define PRCM_CLKOCR_CLKOUT1_SEL_MASK BITS(20, 29) + +/* PRCM_CLKODIV CLKOUTx Dividers */ +#define PRCM_CLKODIV 0x188 +#define PRCM_CLKODIV_CLKOUT0_DIV_SHIFT 0 +#define PRCM_CLKODIV_CLKOUT0_DIV_MASK BITS(0, 2) +#define PRCM_CLKODIV_CLKOUT1_DIV_SHIFT 16 +#define PRCM_CLKODIV_CLKOUT1_DIV_MASK BITS(16, 18) + +#define PRCM_MMIP_LS_CLAMP_SET 0x420 +#define PRCM_MMIP_LS_CLAMP_CLR 0x424 +#define PRCM_DDR_SUBSYS_APE_MINBW 0x438 /* Miscellaneous unit registers */ -#define PRCM_DSI_SW_RESET (_PRCMU_BASE + 0x324) -#define PRCM_GPIOCR (_PRCMU_BASE + 0x138) -#define PRCM_GPIOCR_DBG_STM_MOD_CMD1 0x800 -#define PRCM_GPIOCR_DBG_UARTMOD_CMD0 0x1 - +#define PRCM_DSI_SW_RESET 0x324 +#define PRCM_RESOUTN_SET_OFFSET 0x214 +#define PRCM_RESOUTN_CLR_OFFSET 0x218 -#endif /* __MACH_PRCMU__REGS_H */ +#endif diff --git a/drivers/mfd/db5500-prcmu.c b/drivers/mfd/db5500-prcmu.c index 9dbb3cab4a6..aee5ac397f9 100644 --- a/drivers/mfd/db5500-prcmu.c +++ b/drivers/mfd/db5500-prcmu.c @@ -19,13 +19,22 @@ #include <linux/irq.h> #include <linux/jiffies.h> #include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/regulator/db5500-prcmu.h> +#include <linux/regulator/machine.h> #include <linux/interrupt.h> -#include <linux/mfd/db5500-prcmu.h> +#include <linux/mfd/dbx500-prcmu.h> #include <mach/hardware.h> #include <mach/irqs.h> #include <mach/db5500-regs.h> +#include <mach/prcmu-debug.h> + #include "db5500-prcmu-regs.h" +#define PRCMU_FW_VERSION_OFFSET 0xA4 +#define PRCM_SW_RST_REASON (tcdm_base + 0xFF8) /* 2 bytes */ + #define _PRCM_MB_HEADER (tcdm_base + 0xFE8) #define PRCM_REQ_MB0_HEADER (_PRCM_MB_HEADER + 0x0) #define PRCM_REQ_MB1_HEADER (_PRCM_MB_HEADER + 0x1) @@ -64,6 +73,52 @@ #define PRCM_ACK_MB6 (tcdm_base + 0xF0C) #define PRCM_ACK_MB7 (tcdm_base + 0xF08) +/* Share info */ +#define PRCM_SHARE_INFO (tcdm_base + 0xEC8) + +#define PRCM_SHARE_INFO_HOTDOG (PRCM_SHARE_INFO + 62) + +/* Mailbox 0 REQs */ +#define PRCM_REQ_MB0_AP_POWER_STATE (PRCM_REQ_MB0 + 0x0) +#define PRCM_REQ_MB0_ULP_CLOCK_STATE (PRCM_REQ_MB0 + 0x1) +#define PRCM_REQ_MB0_AP_PLL_STATE (PRCM_REQ_MB0 + 0x2) +#define PRCM_REQ_MB0_DDR_STATE (PRCM_REQ_MB0 + 0x3) +#define PRCM_REQ_MB0_ESRAM0_STATE (PRCM_REQ_MB0 + 0x4) +#define PRCM_REQ_MB0_WAKEUP_DBB (PRCM_REQ_MB0 + 0x8) +#define PRCM_REQ_MB0_WAKEUP_ABB (PRCM_REQ_MB0 + 0xC) + +/* Mailbox 0 ACKs */ +#define PRCM_ACK_MB0_AP_PWRSTTR_STATUS (PRCM_ACK_MB0 + 0x0) +#define PRCM_ACK_MB0_READ_POINTER (PRCM_ACK_MB0 + 0x1) +#define PRCM_ACK_MB0_WAKEUP_0_DBB (PRCM_ACK_MB0 + 0x4) +#define PRCM_ACK_MB0_WAKEUP_0_ABB (PRCM_ACK_MB0 + 0x8) +#define PRCM_ACK_MB0_WAKEUP_1_DBB (PRCM_ACK_MB0 + 0x28) +#define PRCM_ACK_MB0_WAKEUP_1_ABB (PRCM_ACK_MB0 + 0x2C) +#define PRCM_ACK_MB0_EVENT_ABB_NUMBERS 20 + +/* Request mailbox 1 fields. */ +#define PRCM_REQ_MB1_ARM_OPP (PRCM_REQ_MB1 + 0x0) +#define PRCM_REQ_MB1_APE_OPP (PRCM_REQ_MB1 + 0x1) + +/* Mailbox 1 ACKs */ +#define PRCM_ACK_MB1_CURRENT_ARM_OPP (PRCM_ACK_MB1 + 0x0) +#define PRCM_ACK_MB1_CURRENT_APE_OPP (PRCM_ACK_MB1 + 0x1) +#define PRCM_ACK_MB1_ARM_VOLT_STATUS (PRCM_ACK_MB1 + 0x2) +#define PRCM_ACK_MB1_APE_VOLT_STATUS (PRCM_ACK_MB1 + 0x3) + +/* Mailbox 2 REQs */ +#define PRCM_REQ_MB2_EPOD_CLIENT (PRCM_REQ_MB2 + 0x0) +#define PRCM_REQ_MB2_EPOD_STATE (PRCM_REQ_MB2 + 0x1) +#define PRCM_REQ_MB2_CLK_CLIENT (PRCM_REQ_MB2 + 0x2) +#define PRCM_REQ_MB2_CLK_STATE (PRCM_REQ_MB2 + 0x3) +#define PRCM_REQ_MB2_PLL_CLIENT (PRCM_REQ_MB2 + 0x4) +#define PRCM_REQ_MB2_PLL_STATE (PRCM_REQ_MB2 + 0x5) + +/* Mailbox 2 ACKs */ +#define PRCM_ACK_MB2_EPOD_STATUS (PRCM_ACK_MB2 + 0x2) +#define PRCM_ACK_MB2_CLK_STATUS (PRCM_ACK_MB2 + 0x6) +#define PRCM_ACK_MB2_PLL_STATUS (PRCM_ACK_MB2 + 0xA) + enum mb_return_code { RC_SUCCESS, RC_FAIL, @@ -71,12 +126,50 @@ enum mb_return_code { /* Mailbox 0 headers. */ enum mb0_header { - /* request */ - RMB0H_PWR_STATE_TRANS = 1, - RMB0H_WAKE_UP_CFG, - RMB0H_RD_WAKE_UP_ACK, /* acknowledge */ - AMB0H_WAKE_UP = 1, + MB0H_WAKE_UP = 0, + /* request */ + MB0H_PWR_STATE_TRANS, + MB0H_WAKE_UP_CFG, + MB0H_RD_WAKE_UP_ACK, +}; + +/* Mailbox 1 headers.*/ +enum mb1_header { + MB1H_ARM_OPP = 1, + MB1H_APE_OPP, + MB1H_ARM_APE_OPP, +}; + +/* Mailbox 2 headers. */ +enum mb2_header { + MB2H_EPOD_REQUEST = 1, + MB2H_CLK_REQUEST, + MB2H_PLL_REQUEST, +}; + +/* Mailbox 3 headers. */ +enum mb3_header { + MB3H_REFCLK_REQUEST = 1, +}; + +enum sysclk_state { + SYSCLK_OFF, + SYSCLK_ON, +}; + +/* Mailbox 4 headers */ +enum mb4_header { + MB4H_CFG_HOTDOG = 7, + MB4H_CFG_HOTMON = 8, + MB4H_CFG_HOTPERIOD = 10, +}; + +/* Mailbox 4 ACK headers */ +enum mb4_ack_header { + MB4H_ACK_CFG_HOTDOG = 5, + MB4H_ACK_CFG_HOTMON = 6, + MB4H_ACK_CFG_HOTPERIOD = 8, }; /* Mailbox 5 headers. */ @@ -85,6 +178,69 @@ enum mb5_header { MB5H_I2C_READ, }; +enum db5500_arm_opp { + DB5500_ARM_100_OPP = 1, + DB5500_ARM_50_OPP, + DB5500_ARM_EXT_OPP, +}; + +enum db5500_ape_opp { + DB5500_APE_100_OPP = 1, + DB5500_APE_50_OPP +}; + +enum epod_state { + EPOD_OFF, + EPOD_ON, +}; +enum epod_onoffret_state { + EPOD_OOR_OFF, + EPOD_OOR_RET, + EPOD_OOR_ON, +}; +enum db5500_prcmu_pll { + DB5500_PLL_SOC0, + DB5500_PLL_SOC1, + DB5500_PLL_DDR, + DB5500_NUM_PLL_ID, +}; + +enum db5500_prcmu_clk { + DB5500_MSP1CLK, + DB5500_CDCLK, + DB5500_IRDACLK, + DB5500_TVCLK, + DB5500_NUM_CLK_CLIENTS, +}; + +enum on_off_ret { + OFF_ST, + RET_ST, + ON_ST, +}; + +enum db5500_ap_pwr_state { + DB5500_AP_SLEEP = 2, + DB5500_AP_DEEP_SLEEP, + DB5500_AP_IDLE, +}; + +/* Request mailbox 3 fields */ +#define PRCM_REQ_MB3_REFCLK_MGT (PRCM_REQ_MB3 + 0x0) + +/* Ack. mailbox 3 fields */ +#define PRCM_ACK_MB3_REFCLK_REQ (PRCM_ACK_MB3 + 0x0) + + +/* Request mailbox 4 fields */ +#define PRCM_REQ_MB4_HOTDOG_THRESHOLD (PRCM_REQ_MB4 + 32) +#define PRCM_REQ_MB4_HOT_PERIOD (PRCM_REQ_MB4 + 34) +#define PRCM_REQ_MB4_HOTMON_LOW (PRCM_REQ_MB4 + 36) +#define PRCM_REQ_MB4_HOTMON_HIGH (PRCM_REQ_MB4 + 38) + +/* Ack. mailbox 4 field */ +#define PRCM_ACK_MB4_REQUESTS (PRCM_ACK_MB4 + 0x0) + /* Request mailbox 5 fields. */ #define PRCM_REQ_MB5_I2C_SLAVE (PRCM_REQ_MB5 + 0) #define PRCM_REQ_MB5_I2C_REG (PRCM_REQ_MB5 + 1) @@ -109,26 +265,190 @@ enum mb5_header { #define PRCMU_DSI_CLOCK_SETTING 0x00000128 /* TVCLK_MGT PLLSW=001 (PLLSOC0) PLLDIV=0x13, = 19.05 MHZ */ #define PRCMU_DSI_LP_CLOCK_SETTING 0x00000135 -#define PRCMU_PLLDSI_FREQ_SETTING 0x0004013C +#define PRCMU_PLLDSI_FREQ_SETTING 0x00020121 #define PRCMU_DSI_PLLOUT_SEL_SETTING 0x00000002 -#define PRCMU_ENABLE_ESCAPE_CLOCK_DIV 0x03000101 +#define PRCMU_ENABLE_ESCAPE_CLOCK_DIV 0x03000201 #define PRCMU_DISABLE_ESCAPE_CLOCK_DIV 0x00000101 #define PRCMU_ENABLE_PLLDSI 0x00000001 #define PRCMU_DISABLE_PLLDSI 0x00000000 #define PRCMU_DSI_RESET_SW 0x00000003 +#define PRCMU_RESOUTN0_PIN 0x00000001 +#define PRCMU_RESOUTN1_PIN 0x00000002 +#define PRCMU_RESOUTN2_PIN 0x00000004 #define PRCMU_PLLDSI_LOCKP_LOCKED 0x3 /* + * Wakeups/IRQs + */ + +#define WAKEUP_BIT_RTC BIT(0) +#define WAKEUP_BIT_RTT0 BIT(1) +#define WAKEUP_BIT_RTT1 BIT(2) +#define WAKEUP_BIT_CD_IRQ BIT(3) +#define WAKEUP_BIT_SRP_TIM BIT(4) +#define WAKEUP_BIT_APE_REQ BIT(5) +#define WAKEUP_BIT_USB BIT(6) +#define WAKEUP_BIT_ABB BIT(7) +#define WAKEUP_BIT_LOW_POWER_AUDIO BIT(8) +#define WAKEUP_BIT_TEMP_SENSOR_LOW BIT(9) +#define WAKEUP_BIT_ARM BIT(10) +#define WAKEUP_BIT_AC_WAKE_ACK BIT(11) +#define WAKEUP_BIT_TEMP_SENSOR_HIGH BIT(12) +#define WAKEUP_BIT_MODEM_SW_RESET_REQ BIT(20) +#define WAKEUP_BIT_GPIO0 BIT(23) +#define WAKEUP_BIT_GPIO1 BIT(24) +#define WAKEUP_BIT_GPIO2 BIT(25) +#define WAKEUP_BIT_GPIO3 BIT(26) +#define WAKEUP_BIT_GPIO4 BIT(27) +#define WAKEUP_BIT_GPIO5 BIT(28) +#define WAKEUP_BIT_GPIO6 BIT(29) +#define WAKEUP_BIT_GPIO7 BIT(30) +#define WAKEUP_BIT_AC_REL_ACK BIT(30) + +/* + * This vector maps irq numbers to the bits in the bit field used in + * communication with the PRCMU firmware. + * + * The reason for having this is to keep the irq numbers contiguous even though + * the bits in the bit field are not. (The bits also have a tendency to move + * around, to further complicate matters.) + */ +#define IRQ_INDEX(_name) ((IRQ_DB5500_PRCMU_##_name) - IRQ_DB5500_PRCMU_BASE) +#define IRQ_ENTRY(_name)[IRQ_INDEX(_name)] = (WAKEUP_BIT_##_name) +static u32 prcmu_irq_bit[NUM_DB5500_PRCMU_WAKEUPS] = { + IRQ_ENTRY(RTC), + IRQ_ENTRY(RTT0), + IRQ_ENTRY(RTT1), + IRQ_ENTRY(CD_IRQ), + IRQ_ENTRY(SRP_TIM), + IRQ_ENTRY(APE_REQ), + IRQ_ENTRY(USB), + IRQ_ENTRY(ABB), + IRQ_ENTRY(LOW_POWER_AUDIO), + IRQ_ENTRY(TEMP_SENSOR_LOW), + IRQ_ENTRY(TEMP_SENSOR_HIGH), + IRQ_ENTRY(ARM), + IRQ_ENTRY(AC_WAKE_ACK), + IRQ_ENTRY(MODEM_SW_RESET_REQ), + IRQ_ENTRY(GPIO0), + IRQ_ENTRY(GPIO1), + IRQ_ENTRY(GPIO2), + IRQ_ENTRY(GPIO3), + IRQ_ENTRY(GPIO4), + IRQ_ENTRY(GPIO5), + IRQ_ENTRY(GPIO6), + IRQ_ENTRY(GPIO7), + IRQ_ENTRY(AC_REL_ACK), +}; + +#define VALID_WAKEUPS (BIT(NUM_PRCMU_WAKEUP_INDICES) - 1) +#define WAKEUP_ENTRY(_name)[PRCMU_WAKEUP_INDEX_##_name] = (WAKEUP_BIT_##_name) +static u32 prcmu_wakeup_bit[NUM_PRCMU_WAKEUP_INDICES] = { + WAKEUP_ENTRY(RTC), + WAKEUP_ENTRY(RTT0), + WAKEUP_ENTRY(RTT1), + WAKEUP_ENTRY(CD_IRQ), + WAKEUP_ENTRY(USB), + WAKEUP_ENTRY(ABB), + WAKEUP_ENTRY(ARM) +}; + +/* * mb0_transfer - state needed for mailbox 0 communication. - * @lock: The transaction lock. + * @lock The transaction lock. + * @dbb_irqs_lock lock used for (un)masking DBB wakeup interrupts + * @mask_work: Work structure used for (un)masking wakeup interrupts. + * @req: Request data that need to persist between requests. */ static struct { spinlock_t lock; + spinlock_t dbb_irqs_lock; + struct work_struct mask_work; + struct { + u32 dbb_irqs; + u32 dbb_wakeups; + u32 abb_events; + } req; } mb0_transfer; + +/* + * mb1_transfer - state needed for mailbox 1 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @req_arm_opp Requested arm opp + * @req_ape_opp Requested ape opp + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + u8 req_arm_opp; + u8 req_ape_opp; + struct { + u8 header; + u8 arm_opp; + u8 ape_opp; + u8 arm_voltage_st; + u8 ape_voltage_st; + } ack; +} mb1_transfer; + +/* + * mb2_transfer - state needed for mailbox 2 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @req: Request data that need to persist between requests. + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + struct { + u8 epod_st[DB5500_NUM_EPOD_ID]; + u8 pll_st[DB5500_NUM_PLL_ID]; + } req; + struct { + u8 header; + u8 status; + } ack; +} mb2_transfer; + +/* + * mb3_transfer - state needed for mailbox 3 communication. + * @sysclk_lock: A lock used to handle concurrent sysclk requests. + * @sysclk_work: Work structure used for sysclk requests. + * @req_st: Requested clock state. + * @ack: Acknowledgement data + */ +static struct { + struct mutex sysclk_lock; + struct completion sysclk_work; + enum sysclk_state req_st; + struct { + u8 header; + u8 status; + } ack; +} mb3_transfer; + +/* + * mb4_transfer - state needed for mailbox 4 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @ack: Acknowledgement data + */ +static struct { + struct mutex lock; + struct completion work; + struct { + u8 header; + u8 status; + } ack; +} mb4_transfer; + /* * mb5_transfer - state needed for mailbox 5 communication. * @lock: The transaction lock. @@ -145,9 +465,612 @@ static struct { } ack; } mb5_transfer; +/* Spinlocks */ +static DEFINE_SPINLOCK(clkout_lock); + /* PRCMU TCDM base IO address. */ static __iomem void *tcdm_base; +struct clk_mgt { + unsigned int offset; + u32 pllsw; + u32 div; + bool scalable; + bool force50; +}; + +/* PRCMU Firmware Details */ +static struct { + u16 board; + u8 fw_version; + u8 api_version; +} prcmu_version; + +static DEFINE_SPINLOCK(clk_mgt_lock); + +#define CLK_MGT_ENTRY(_name, _scalable)[PRCMU_##_name] = { \ + .offset = DB5500_PRCM_##_name##_MGT, \ + .scalable = _scalable, \ +} + +static struct clk_mgt clk_mgt[PRCMU_NUM_REG_CLOCKS] = { + CLK_MGT_ENTRY(SGACLK, true), + CLK_MGT_ENTRY(UARTCLK, false), + CLK_MGT_ENTRY(MSP02CLK, false), + CLK_MGT_ENTRY(I2CCLK, false), + [PRCMU_SDMMCCLK] { + .offset = DB5500_PRCM_SDMMCCLK_MGT, + .force50 = true, + .scalable = false, + + }, + [PRCMU_SPARE1CLK] { + .offset = DB5500_PRCM_SPARE1CLK_MGT, + .force50 = true, + .scalable = false, + + }, + CLK_MGT_ENTRY(PER1CLK, false), + CLK_MGT_ENTRY(PER2CLK, true), + CLK_MGT_ENTRY(PER3CLK, true), + CLK_MGT_ENTRY(PER5CLK, false), /* used for SPI */ + CLK_MGT_ENTRY(PER6CLK, true), + CLK_MGT_ENTRY(PWMCLK, false), + CLK_MGT_ENTRY(IRDACLK, false), + CLK_MGT_ENTRY(IRRCCLK, false), + CLK_MGT_ENTRY(HDMICLK, false), + CLK_MGT_ENTRY(APEATCLK, false), + CLK_MGT_ENTRY(APETRACECLK, true), + CLK_MGT_ENTRY(MCDECLK, true), + CLK_MGT_ENTRY(DSIALTCLK, false), + CLK_MGT_ENTRY(DMACLK, true), + CLK_MGT_ENTRY(B2R2CLK, true), + CLK_MGT_ENTRY(TVCLK, false), + CLK_MGT_ENTRY(RNGCLK, false), + CLK_MGT_ENTRY(SIACLK, false), + CLK_MGT_ENTRY(SVACLK, false), +}; + +bool db5500_prcmu_is_ac_wake_requested(void) +{ + return false; +} + +/** + * prcmu_config_clkout - Configure one of the programmable clock outputs. + * @clkout: The CLKOUT number (0 or 1). + * @source: Clock source. + * @div: The divider to be applied. + * + * Configures one of the programmable clock outputs (CLKOUTs). + */ +int prcmu_config_clkout(u8 clkout, u8 source, u8 div) +{ + static bool configured[2] = {false, false}; + int r = 0; + unsigned long flags; + u32 sel_val; + u32 div_val; + u32 sel_bits; + u32 div_bits; + u32 sel_mask; + u32 div_mask; + u8 sel0 = CLKOUT_SEL0_SEL_CLK; + u16 sel = 0; + + BUG_ON(clkout > DB5500_CLKOUT1); + BUG_ON(source > DB5500_CLKOUT_IRDACLK); + BUG_ON(div > 7); + + switch (source) { + case DB5500_CLKOUT_REF_CLK_SEL0: + sel0 = CLKOUT_SEL0_REF_CLK; + break; + case DB5500_CLKOUT_RTC_CLK0_SEL0: + sel0 = CLKOUT_SEL0_RTC_CLK0; + break; + case DB5500_CLKOUT_ULP_CLK_SEL0: + sel0 = CLKOUT_SEL0_ULP_CLK; + break; + case DB5500_CLKOUT_STATIC0: + sel = CLKOUT_SEL_STATIC0; + break; + case DB5500_CLKOUT_REFCLK: + sel = CLKOUT_SEL_REFCLK; + break; + case DB5500_CLKOUT_ULPCLK: + sel = CLKOUT_SEL_ULPCLK; + break; + case DB5500_CLKOUT_ARMCLK: + sel = CLKOUT_SEL_ARMCLK; + break; + case DB5500_CLKOUT_SYSACC0CLK: + sel = CLKOUT_SEL_SYSACC0CLK; + break; + case DB5500_CLKOUT_SOC0PLLCLK: + sel = CLKOUT_SEL_SOC0PLLCLK; + break; + case DB5500_CLKOUT_SOC1PLLCLK: + sel = CLKOUT_SEL_SOC1PLLCLK; + break; + case DB5500_CLKOUT_DDRPLLCLK: + sel = CLKOUT_SEL_DDRPLLCLK; + break; + case DB5500_CLKOUT_TVCLK: + sel = CLKOUT_SEL_TVCLK; + break; + case DB5500_CLKOUT_IRDACLK: + sel = CLKOUT_SEL_IRDACLK; + break; + } + + switch (clkout) { + case DB5500_CLKOUT0: + sel_mask = PRCM_CLKOCR_CLKOUT0_SEL0_MASK | + PRCM_CLKOCR_CLKOUT0_SEL_MASK; + sel_bits = ((sel0 << PRCM_CLKOCR_CLKOUT0_SEL0_SHIFT) | + (sel << PRCM_CLKOCR_CLKOUT0_SEL_SHIFT)); + div_mask = PRCM_CLKODIV_CLKOUT0_DIV_MASK; + div_bits = div << PRCM_CLKODIV_CLKOUT0_DIV_SHIFT; + break; + case DB5500_CLKOUT1: + sel_mask = PRCM_CLKOCR_CLKOUT1_SEL0_MASK | + PRCM_CLKOCR_CLKOUT1_SEL_MASK; + sel_bits = ((sel0 << PRCM_CLKOCR_CLKOUT1_SEL0_SHIFT) | + (sel << PRCM_CLKOCR_CLKOUT1_SEL_SHIFT)); + div_mask = PRCM_CLKODIV_CLKOUT1_DIV_MASK; + div_bits = div << PRCM_CLKODIV_CLKOUT1_DIV_SHIFT; + break; + } + + spin_lock_irqsave(&clkout_lock, flags); + + if (configured[clkout]) { + r = -EINVAL; + goto unlock_and_return; + } + + sel_val = readl(_PRCMU_BASE + PRCM_CLKOCR); + writel((sel_bits | (sel_val & ~sel_mask)), + (_PRCMU_BASE + PRCM_CLKOCR)); + + div_val = readl(_PRCMU_BASE + PRCM_CLKODIV); + writel((div_bits | (div_val & ~div_mask)), + (_PRCMU_BASE + PRCM_CLKODIV)); + + configured[clkout] = true; + +unlock_and_return: + spin_unlock_irqrestore(&clkout_lock, flags); + + return r; +} + +static int request_sysclk(bool enable) +{ + int r; + + r = 0; + mutex_lock(&mb3_transfer.sysclk_lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(3)) + cpu_relax(); + + if (enable) + mb3_transfer.req_st = SYSCLK_ON; + else + mb3_transfer.req_st = SYSCLK_OFF; + + writeb(mb3_transfer.req_st, (PRCM_REQ_MB3_REFCLK_MGT)); + + writeb(MB3H_REFCLK_REQUEST, (PRCM_REQ_MB3_HEADER)); + writel(MBOX_BIT(3), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + + /* + * The firmware only sends an ACK if we want to enable the + * SysClk, and it succeeds. + */ + if (!wait_for_completion_timeout(&mb3_transfer.sysclk_work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + __func__); + r = -EIO; + WARN(1, "Failed to set sysclk"); + goto unlock_and_return; + } + + if ((mb3_transfer.ack.header != MB3H_REFCLK_REQUEST) || + (mb3_transfer.ack.status != mb3_transfer.req_st)) { + r = -EIO; + } + +unlock_and_return: + mutex_unlock(&mb3_transfer.sysclk_lock); + + return r; +} + +static int request_timclk(bool enable) +{ + u32 val = (PRCM_TCR_DOZE_MODE | PRCM_TCR_TENSEL_MASK); + + if (!enable) + val |= PRCM_TCR_STOP_TIMERS; + writel(val, _PRCMU_BASE + PRCM_TCR); + + return 0; +} + +static int request_clk(u8 clock, bool enable) +{ + int r = 0; + + BUG_ON(clock >= DB5500_NUM_CLK_CLIENTS); + + mutex_lock(&mb2_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) + cpu_relax(); + + /* fill in mailbox */ + writeb(clock, PRCM_REQ_MB2_CLK_CLIENT); + writeb(enable, PRCM_REQ_MB2_CLK_STATE); + + writeb(MB2H_CLK_REQUEST, PRCM_REQ_MB2_HEADER); + + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(500))) { + pr_err("prcmu: request_clk() failed.\n"); + r = -EIO; + WARN(1, "Failed in request_clk"); + goto unlock_and_return; + } + if (mb2_transfer.ack.status != RC_SUCCESS || + mb2_transfer.ack.header != MB2H_CLK_REQUEST) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + return r; +} + +static int request_reg_clock(u8 clock, bool enable) +{ + u32 val; + unsigned long flags; + + WARN_ON(!clk_mgt[clock].offset); + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + val = readl(_PRCMU_BASE + clk_mgt[clock].offset); + if (enable) { + val |= (PRCM_CLK_MGT_CLKEN | clk_mgt[clock].pllsw); + } else { + clk_mgt[clock].pllsw = (val & PRCM_CLK_MGT_CLKPLLSW_MASK); + val &= ~(PRCM_CLK_MGT_CLKEN | PRCM_CLK_MGT_CLKPLLSW_MASK); + } + writel(val, (_PRCMU_BASE + clk_mgt[clock].offset)); + + /* Release the HW semaphore. */ + writel(0, _PRCMU_BASE + PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + + return 0; +} + +/* + * request_pll() - Request for a pll to be enabled or disabled. + * @pll: The pll for which the request is made. + * @enable: Whether the clock should be enabled (true) or disabled (false). + * + * This function should only be used by the clock implementation. + * Do not use it from any other place! + */ +static int request_pll(u8 pll, bool enable) +{ + int r = 0; + + BUG_ON(pll >= DB5500_NUM_PLL_ID); + mutex_lock(&mb2_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) + cpu_relax(); + + mb2_transfer.req.pll_st[pll] = enable; + + /* fill in mailbox */ + writeb(pll, PRCM_REQ_MB2_PLL_CLIENT); + writeb(mb2_transfer.req.pll_st[pll], PRCM_REQ_MB2_PLL_STATE); + + writeb(MB2H_PLL_REQUEST, PRCM_REQ_MB2_HEADER); + + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(500))) { + pr_err("prcmu: set_pll() failed.\n" + "prcmu: Please check your firmware version.\n"); + r = -EIO; + WARN(1, "Failed to set pll"); + goto unlock_and_return; + } + if (mb2_transfer.ack.status != RC_SUCCESS || + mb2_transfer.ack.header != MB2H_PLL_REQUEST) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + + return r; +} + +/** + * db5500_prcmu_request_clock() - Request for a clock to be enabled or disabled. + * @clock: The clock for which the request is made. + * @enable: Whether the clock should be enabled (true) or disabled (false). + * + * This function should only be used by the clock implementation. + * Do not use it from any other place! + */ +int db5500_prcmu_request_clock(u8 clock, bool enable) +{ + /* MSP1 & CD clocks are handled by FW */ + if (clock == PRCMU_MSP1CLK) + return request_clk(DB5500_MSP1CLK, enable); + else if (clock == PRCMU_CDCLK) + return request_clk(DB5500_CDCLK, enable); + else if (clock < PRCMU_NUM_REG_CLOCKS) + return request_reg_clock(clock, enable); + else if (clock == PRCMU_TIMCLK) + return request_timclk(enable); + else if (clock == PRCMU_PLLSOC0) + return request_pll(DB5500_PLL_SOC0, enable); + else if (clock == PRCMU_PLLSOC1) + return request_pll(DB5500_PLL_SOC1, enable); + else if (clock == PRCMU_PLLDDR) + return request_pll(DB5500_PLL_DDR, enable); + else if (clock == PRCMU_SYSCLK) + return request_sysclk(enable); + else + return -EINVAL; +} + +/* This function should only be called while mb0_transfer.lock is held. */ +static void config_wakeups(void) +{ + static u32 last_dbb_events; + static u32 last_abb_events; + u32 dbb_events; + u32 abb_events; + + dbb_events = mb0_transfer.req.dbb_irqs | mb0_transfer.req.dbb_wakeups; + + abb_events = mb0_transfer.req.abb_events; + + if ((dbb_events == last_dbb_events) && (abb_events == last_abb_events)) + return; + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + cpu_relax(); + + writel(dbb_events, PRCM_REQ_MB0_WAKEUP_DBB); + writel(abb_events, PRCM_REQ_MB0_WAKEUP_ABB); + writeb(MB0H_WAKE_UP_CFG, PRCM_REQ_MB0_HEADER); + writel(MBOX_BIT(0), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + + last_dbb_events = dbb_events; + last_abb_events = abb_events; +} + +int db5500_prcmu_config_esram0_deep_sleep(u8 state) +{ + unsigned long flags; + + if ((state > ESRAM0_DEEP_SLEEP_STATE_RET) || + (state < ESRAM0_DEEP_SLEEP_STATE_OFF)) + return -EINVAL; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + if (state == ESRAM0_DEEP_SLEEP_STATE_RET) + writeb(RET_ST, PRCM_REQ_MB0_ESRAM0_STATE); + else + writeb(OFF_ST, PRCM_REQ_MB0_ESRAM0_STATE); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); + + return 0; +} + +int db5500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) +{ + int r = 0; + unsigned long flags; + + /* Deep Idle is not supported in DB5500 */ + BUG_ON((state < PRCMU_AP_SLEEP) || (state >= PRCMU_AP_DEEP_IDLE)); + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + cpu_relax(); + + switch (state) { + case PRCMU_AP_IDLE: + writeb(DB5500_AP_IDLE, PRCM_REQ_MB0_AP_POWER_STATE); + /* TODO: Can be high latency */ + writeb(DDR_PWR_STATE_UNCHANGED, PRCM_REQ_MB0_DDR_STATE); + break; + case PRCMU_AP_SLEEP: + writeb(DB5500_AP_SLEEP, PRCM_REQ_MB0_AP_POWER_STATE); + break; + case PRCMU_AP_DEEP_SLEEP: + writeb(DB5500_AP_DEEP_SLEEP, PRCM_REQ_MB0_AP_POWER_STATE); + break; + default: + r = -EINVAL; + goto unlock_return; + } + writeb((keep_ap_pll ? 1 : 0), PRCM_REQ_MB0_AP_PLL_STATE); + writeb((keep_ulp_clk ? 1 : 0), PRCM_REQ_MB0_ULP_CLOCK_STATE); + + writeb(MB0H_PWR_STATE_TRANS, PRCM_REQ_MB0_HEADER); + writel(MBOX_BIT(0), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + +unlock_return: + spin_unlock_irqrestore(&mb0_transfer.lock, flags); + + return r; +} + +void db5500_prcmu_enable_wakeups(u32 wakeups) +{ + unsigned long flags; + u32 bits; + int i; + + BUG_ON(wakeups != (wakeups & VALID_WAKEUPS)); + + for (i = 0, bits = 0; i < NUM_PRCMU_WAKEUP_INDICES; i++) { + if (wakeups & BIT(i)) { + if (prcmu_wakeup_bit[i] == 0) + WARN(1, "WAKEUP NOT SUPPORTED"); + else + bits |= prcmu_wakeup_bit[i]; + } + } + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + mb0_transfer.req.dbb_wakeups = bits; + config_wakeups(); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +void db5500_prcmu_config_abb_event_readout(u32 abb_events) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + mb0_transfer.req.abb_events = abb_events; + config_wakeups(); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +void db5500_prcmu_get_abb_event_buffer(void __iomem **buf) +{ + if (readb(PRCM_ACK_MB0_READ_POINTER) & 1) + *buf = (PRCM_ACK_MB0_WAKEUP_1_ABB); + else + *buf = (PRCM_ACK_MB0_WAKEUP_0_ABB); +} + +/* This function should be called with lock */ +static int mailbox4_request(u8 mb4_request, u8 ack_request) +{ + int ret = 0; + + writeb(mb4_request, PRCM_REQ_MB4_HEADER); + writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + + if (!wait_for_completion_timeout(&mb4_transfer.work, + msecs_to_jiffies(500))) { + pr_err("prcmu: MB4 request %d failed", mb4_request); + ret = -EIO; + WARN(1, "prcmu: failed mb4 request"); + goto failed; + } + + if (mb4_transfer.ack.header != ack_request || + mb4_transfer.ack.status != RC_SUCCESS) + ret = -EIO; +failed: + return ret; +} + +int db5500_prcmu_get_hotdog(void) +{ + return readw(PRCM_SHARE_INFO_HOTDOG); +} + +int db5500_prcmu_config_hotdog(u8 threshold) +{ + int r = 0; + + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writew(threshold, PRCM_REQ_MB4_HOTDOG_THRESHOLD); + r = mailbox4_request(MB4H_CFG_HOTDOG, MB4H_ACK_CFG_HOTDOG); + + mutex_unlock(&mb4_transfer.lock); + + return r; +} + +int db5500_prcmu_config_hotmon(u8 low, u8 high) +{ + int r = 0; + + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writew(low, PRCM_REQ_MB4_HOTMON_LOW); + writew(high, PRCM_REQ_MB4_HOTMON_HIGH); + + r = mailbox4_request(MB4H_CFG_HOTMON, MB4H_ACK_CFG_HOTMON); + + mutex_unlock(&mb4_transfer.lock); + + return r; +} + +static int config_hot_period(u16 val) +{ + int r = 0; + + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writew(val, PRCM_REQ_MB4_HOT_PERIOD); + r = mailbox4_request(MB4H_CFG_HOTPERIOD, MB4H_ACK_CFG_HOTPERIOD); + + mutex_unlock(&mb4_transfer.lock); + + return r; +} + +/* + * period in milli seconds + */ +int db5500_prcmu_start_temp_sense(u16 period) +{ + if (period == 0xFFFF) + return -EINVAL; + + return config_hot_period(period); +} + +int db5500_prcmu_stop_temp_sense(void) +{ + return config_hot_period(0xFFFF); +} + /** * db5500_prcmu_abb_read() - Read register value(s) from the ABB. * @slave: The I2C slave address. @@ -167,14 +1090,14 @@ int db5500_prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) mutex_lock(&mb5_transfer.lock); - while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) cpu_relax(); writeb(slave, PRCM_REQ_MB5_I2C_SLAVE); writeb(reg, PRCM_REQ_MB5_I2C_REG); writeb(size, PRCM_REQ_MB5_I2C_SIZE); writeb(MB5H_I2C_READ, PRCM_REQ_MB5_HEADER); - writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); + writel(MBOX_BIT(5), _PRCMU_BASE + PRCM_MBOX_CPU_SET); wait_for_completion(&mb5_transfer.work); r = 0; @@ -208,7 +1131,7 @@ int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) mutex_lock(&mb5_transfer.lock); - while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) cpu_relax(); writeb(slave, PRCM_REQ_MB5_I2C_SLAVE); writeb(reg, PRCM_REQ_MB5_I2C_REG); @@ -216,7 +1139,7 @@ int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) memcpy_toio(PRCM_REQ_MB5_I2C_DATA, value, size); writeb(MB5H_I2C_WRITE, PRCM_REQ_MB5_HEADER); - writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); + writel(MBOX_BIT(5), _PRCMU_BASE + PRCM_MBOX_CPU_SET); wait_for_completion(&mb5_transfer.work); if ((mb5_transfer.ack.header == MB5H_I2C_WRITE) && @@ -230,42 +1153,337 @@ int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) return r; } +/** + * db5500_prcmu_set_arm_opp - set the appropriate ARM OPP + * @opp: The new ARM operating point to which transition is to be made + * Returns: 0 on success, non-zero on failure + * + * This function sets the the operating point of the ARM. + */ +int db5500_prcmu_set_arm_opp(u8 opp) +{ + int r; + u8 db5500_opp; + + r = 0; + + switch (opp) { + case ARM_EXTCLK: + db5500_opp = DB5500_ARM_EXT_OPP; + break; + case ARM_50_OPP: + db5500_opp = DB5500_ARM_50_OPP; + break; + case ARM_100_OPP: + db5500_opp = DB5500_ARM_100_OPP; + break; + default: + pr_err("prcmu: %s() received wrong opp value: %d\n", + __func__, opp); + r = -EINVAL; + goto bailout; + } + + mutex_lock(&mb1_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_ARM_OPP, PRCM_REQ_MB1_HEADER); + + writeb(db5500_opp, PRCM_REQ_MB1_ARM_OPP); + writel(MBOX_BIT(1), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + + if (!wait_for_completion_timeout(&mb1_transfer.work, + msecs_to_jiffies(500))) { + r = -EIO; + WARN(1, "prcmu: failed to set arm opp"); + goto unlock_and_return; + } + + if (mb1_transfer.ack.header != MB1H_ARM_OPP || + (mb1_transfer.ack.arm_opp != db5500_opp) || + (mb1_transfer.ack.arm_voltage_st != RC_SUCCESS)) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb1_transfer.lock); +bailout: + if (!r) + prcmu_debug_arm_opp_log(opp); + return r; +} + +static void __init prcmu_ape_clocks_init(void) +{ + u8 opp = db5500_prcmu_get_ape_opp(); + unsigned long flags; + int i; + + WARN(opp != APE_100_OPP, "%s: Initial APE OPP (%u) not 100%%?\n", + __func__, opp); + + for (i = 0; i < PRCMU_NUM_REG_CLOCKS; i++) { + struct clk_mgt *clkmgt = &clk_mgt[i]; + u32 clkval; + u32 div; + + if (!clkmgt->scalable && !clkmgt->force50) + continue; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + clkval = readl(_PRCMU_BASE + clkmgt->offset); + div = clkval & PRCM_CLK_MGT_CLKPLLDIV_MASK; + div >>= PRCM_CLK_MGT_CLKPLLDIV_SHIFT; + + if (clkmgt->force50) { + div *= 2; + + clkval &= ~PRCM_CLK_MGT_CLKPLLDIV_MASK; + clkval |= div << PRCM_CLK_MGT_CLKPLLDIV_SHIFT; + writel(clkval, _PRCMU_BASE + clkmgt->offset); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + continue; + } + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + + clkmgt->div = div; + if (!div) + pr_err("%s: scalable clock at offset %#x has zero divisor\n", + __func__, clkmgt->offset); + } +} + +static void prcmu_ape_clocks_scale(u8 opp) +{ + unsigned long irqflags; + unsigned int i; + u32 clkval; + + /* + * Note: calling printk() under the following lock can cause lock + * recursion via clk_enable() for the console UART! + */ + spin_lock_irqsave(&clk_mgt_lock, irqflags); + + /* take a lock on HW (HWSEM)*/ + while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + for (i = 0; i < PRCMU_NUM_REG_CLOCKS; i++) { + u32 divval; + + if (!clk_mgt[i].scalable) + continue; + + clkval = readl(_PRCMU_BASE + clk_mgt[i].offset); + divval = clk_mgt[i].div; + + pr_debug("PRCMU: reg %#x prev clk = 0x%x stored div = 0x%x\n", + clk_mgt[i].offset, clkval, divval); + + if (opp == DB5500_APE_50_OPP) + divval *= 2; + + clkval &= ~PRCM_CLK_MGT_CLKPLLDIV_MASK; + clkval |= divval << PRCM_CLK_MGT_CLKPLLDIV_SHIFT; + + pr_debug("PRCMU: wr 0x%x in reg 0x%x\n", + clkval, clk_mgt[i].offset); + + writel(clkval, _PRCMU_BASE + clk_mgt[i].offset); + } + + /* release lock */ + writel(0, (_PRCMU_BASE + PRCM_SEM)); + + spin_unlock_irqrestore(&clk_mgt_lock, irqflags); +} + +int db5500_prcmu_set_ape_opp(u8 opp) +{ + int ret = 0; + u8 db5500_opp; + + if (opp == db5500_prcmu_get_ape_opp()) + return ret; + + if (cpu_is_u5500v1()) + return -EINVAL; + + switch (opp) { + case APE_100_OPP: + db5500_opp = DB5500_APE_100_OPP; + break; + case APE_50_OPP: + db5500_opp = DB5500_APE_50_OPP; + break; + default: + pr_err("prcmu: %s() received wrong opp value: %d\n", + __func__, opp); + ret = -EINVAL; + goto bailout; + } + + mutex_lock(&mb1_transfer.lock); + + prcmu_ape_clocks_scale(db5500_opp); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_APE_OPP, PRCM_REQ_MB1_HEADER); + writeb(db5500_opp, PRCM_REQ_MB1_APE_OPP); + writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + + if (!wait_for_completion_timeout(&mb1_transfer.work, + msecs_to_jiffies(500))) { + ret = -EIO; + WARN(1, "prcmu: failed to set ape opp to %u", opp); + goto unlock_and_return; + } + + if (mb1_transfer.ack.header != MB1H_APE_OPP || + (mb1_transfer.ack.ape_opp != db5500_opp) || + (mb1_transfer.ack.arm_voltage_st != RC_SUCCESS)) + ret = -EIO; + +unlock_and_return: + mutex_unlock(&mb1_transfer.lock); +bailout: + return ret; +} + +int db5500_prcmu_get_ape_opp(void) +{ + u8 opp = readb(PRCM_ACK_MB1_CURRENT_APE_OPP); + + switch (opp) { + case DB5500_APE_100_OPP: + return APE_100_OPP; + case DB5500_APE_50_OPP: + return APE_50_OPP; + default: + pr_err("prcmu: %s() read unknown opp value: %d\n", + __func__, opp); + return APE_100_OPP; + } +} + +int db5500_prcmu_get_ddr_opp(void) +{ + return readb(_PRCMU_BASE + PRCM_DDR_SUBSYS_APE_MINBW); +} + +int db5500_prcmu_set_ddr_opp(u8 opp) +{ + if (cpu_is_u5500v1()) + return -EINVAL; + + if (opp != DDR_100_OPP && opp != DDR_50_OPP) + return -EINVAL; + + writeb(opp, _PRCMU_BASE + PRCM_DDR_SUBSYS_APE_MINBW); + + return 0; +} + +/** + * db5500_prcmu_get_arm_opp - get the current ARM OPP + * + * Returns: the current ARM OPP + */ +int db5500_prcmu_get_arm_opp(void) +{ + u8 opp = readb(PRCM_ACK_MB1_CURRENT_ARM_OPP); + + switch (opp) { + case DB5500_ARM_EXT_OPP: + return ARM_EXTCLK; + case DB5500_ARM_50_OPP: + return ARM_50_OPP; + case DB5500_ARM_100_OPP: + return ARM_100_OPP; + default: + pr_err("prcmu: %s() read unknown opp value: %d\n", + __func__, opp); + return ARM_100_OPP; + } +} + +int prcmu_resetout(u8 resoutn, u8 state) +{ + int offset; + int pin = -1; + + offset = state > 0 ? PRCM_RESOUTN_SET_OFFSET : PRCM_RESOUTN_CLR_OFFSET; + + switch (resoutn) { + case 0: + pin = PRCMU_RESOUTN0_PIN; + break; + case 1: + pin = PRCMU_RESOUTN1_PIN; + break; + case 2: + pin = PRCMU_RESOUTN2_PIN; + default: + break; + } + + if (pin > 0) + writel(pin, _PRCMU_BASE + offset); + else + return -EINVAL; + + return 0; +} + int db5500_prcmu_enable_dsipll(void) { int i; + int ret = 0; /* Enable DSIPLL_RESETN resets */ - writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_CLR); + writel(PRCMU_RESET_DSIPLL, _PRCMU_BASE + PRCM_APE_RESETN_CLR); /* Unclamp DSIPLL in/out */ - writel(PRCMU_UNCLAMP_DSIPLL, PRCM_MMIP_LS_CLAMP_CLR); + writel(PRCMU_UNCLAMP_DSIPLL, _PRCMU_BASE + PRCM_MMIP_LS_CLAMP_CLR); /* Set DSI PLL FREQ */ - writel(PRCMU_PLLDSI_FREQ_SETTING, PRCM_PLLDSI_FREQ); + writel(PRCMU_PLLDSI_FREQ_SETTING, _PRCMU_BASE + PRCM_PLLDSI_FREQ); writel(PRCMU_DSI_PLLOUT_SEL_SETTING, - PRCM_DSI_PLLOUT_SEL); + _PRCMU_BASE + PRCM_DSI_PLLOUT_SEL); /* Enable Escape clocks */ - writel(PRCMU_ENABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); + writel(PRCMU_ENABLE_ESCAPE_CLOCK_DIV, _PRCMU_BASE + PRCM_DSITVCLK_DIV); /* Start DSI PLL */ - writel(PRCMU_ENABLE_PLLDSI, PRCM_PLLDSI_ENABLE); + writel(PRCMU_ENABLE_PLLDSI, _PRCMU_BASE + PRCM_PLLDSI_ENABLE); /* Reset DSI PLL */ - writel(PRCMU_DSI_RESET_SW, PRCM_DSI_SW_RESET); + writel(PRCMU_DSI_RESET_SW, _PRCMU_BASE + PRCM_DSI_SW_RESET); for (i = 0; i < 10; i++) { - if ((readl(PRCM_PLLDSI_LOCKP) & + if ((readl(_PRCMU_BASE + PRCM_PLLDSI_LOCKP) & PRCMU_PLLDSI_LOCKP_LOCKED) == PRCMU_PLLDSI_LOCKP_LOCKED) break; udelay(100); } + + if ((readl(_PRCMU_BASE + PRCM_PLLDSI_LOCKP) & + PRCMU_PLLDSI_LOCKP_LOCKED) + != PRCMU_PLLDSI_LOCKP_LOCKED) + ret = -EIO; /* Release DSIPLL_RESETN */ - writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_SET); - return 0; + writel(PRCMU_RESET_DSIPLL, _PRCMU_BASE + PRCM_APE_RESETN_SET); + return ret; } int db5500_prcmu_disable_dsipll(void) { /* Disable dsi pll */ - writel(PRCMU_DISABLE_PLLDSI, PRCM_PLLDSI_ENABLE); + writel(PRCMU_DISABLE_PLLDSI, _PRCMU_BASE + PRCM_PLLDSI_ENABLE); /* Disable escapeclock */ - writel(PRCMU_DISABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); + writel(PRCMU_DISABLE_ESCAPE_CLOCK_DIV, _PRCMU_BASE + PRCM_DSITVCLK_DIV); return 0; } @@ -273,27 +1491,132 @@ int db5500_prcmu_set_display_clocks(void) { /* HDMI and TVCLK Should be handled somewhere else */ /* PLLDIV=8, PLLSW=2, CLKEN=1 */ - writel(PRCMU_DSI_CLOCK_SETTING, PRCM_HDMICLK_MGT); + writel(PRCMU_DSI_CLOCK_SETTING, _PRCMU_BASE + DB5500_PRCM_HDMICLK_MGT); /* PLLDIV=14, PLLSW=2, CLKEN=1 */ - writel(PRCMU_DSI_LP_CLOCK_SETTING, PRCM_TVCLK_MGT); + writel(PRCMU_DSI_LP_CLOCK_SETTING, _PRCMU_BASE + DB5500_PRCM_TVCLK_MGT); return 0; } +/** + * db5500_prcmu_system_reset - System reset + * + * Saves the reset reason code and then sets the APE_SOFTRST register which + * fires an interrupt to fw + */ +void db5500_prcmu_system_reset(u16 reset_code) +{ + writew(reset_code, PRCM_SW_RST_REASON); + writel(1, _PRCMU_BASE + PRCM_APE_SOFTRST); +} + +/** + * db5500_prcmu_get_reset_code - Retrieve SW reset reason code + * + * Retrieves the reset reason code stored by prcmu_system_reset() before + * last restart. + */ +u16 db5500_prcmu_get_reset_code(void) +{ + return readw(PRCM_SW_RST_REASON); +} + static void ack_dbb_wakeup(void) { unsigned long flags; spin_lock_irqsave(&mb0_transfer.lock, flags); - while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) cpu_relax(); - writeb(RMB0H_RD_WAKE_UP_ACK, PRCM_REQ_MB0_HEADER); - writel(MBOX_BIT(0), PRCM_MBOX_CPU_SET); + writeb(MB0H_RD_WAKE_UP_ACK, PRCM_REQ_MB0_HEADER); + writel(MBOX_BIT(0), _PRCMU_BASE + PRCM_MBOX_CPU_SET); spin_unlock_irqrestore(&mb0_transfer.lock, flags); } +int db5500_prcmu_set_epod(u16 epod, u8 epod_state) +{ + int r = 0; + bool ram_retention = false; + + /* check argument */ + BUG_ON(epod < DB5500_EPOD_ID_BASE); + BUG_ON(epod_state > EPOD_STATE_ON); + BUG_ON((epod - DB5500_EPOD_ID_BASE) >= DB5500_NUM_EPOD_ID); + + if (epod == DB5500_EPOD_ID_ESRAM12) + ram_retention = true; + + /* check argument */ + BUG_ON(epod_state == EPOD_STATE_RAMRET && !ram_retention); + + /* get lock */ + mutex_lock(&mb2_transfer.lock); + + /* wait for mailbox */ + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) + cpu_relax(); + + /* Retention is allowed only for ESRAM12 */ + if (epod == DB5500_EPOD_ID_ESRAM12) { + switch (epod_state) { + case EPOD_STATE_ON: + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_OOR_ON; + break; + case EPOD_STATE_OFF: + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_OOR_OFF; + break; + case EPOD_STATE_RAMRET: + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_OOR_RET; + break; + default: + r = -EINVAL; + goto unlock_and_return; + break; + } + } else { + if (epod_state == EPOD_STATE_ON) + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_ON; + else if (epod_state == EPOD_STATE_OFF) + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_OFF; + else { + r = -EINVAL; + goto unlock_and_return; + } + } + /* fill in mailbox */ + writeb((epod - DB5500_EPOD_ID_BASE), PRCM_REQ_MB2_EPOD_CLIENT); + writeb(mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE], + PRCM_REQ_MB2_EPOD_STATE); + + writeb(MB2H_EPOD_REQUEST, PRCM_REQ_MB2_HEADER); + + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(500))) { + pr_err("prcmu: set_epod() failed.\n" + "prcmu: Please check your firmware version.\n"); + r = -EIO; + WARN(1, "Failed to set epod"); + goto unlock_and_return; + } + + if (mb2_transfer.ack.status != RC_SUCCESS || + mb2_transfer.ack.header != MB2H_EPOD_REQUEST) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + return r; +} + static inline void print_unknown_header_warning(u8 n, u8 header) { pr_warning("prcmu: Unknown message header (%d) in mailbox %d.\n", @@ -303,11 +1626,25 @@ static inline void print_unknown_header_warning(u8 n, u8 header) static bool read_mailbox_0(void) { bool r; + u32 ev; + unsigned int n; + u8 header; header = readb(PRCM_ACK_MB0_HEADER); switch (header) { - case AMB0H_WAKE_UP: + case MB0H_WAKE_UP: + if (readb(PRCM_ACK_MB0_READ_POINTER) & 1) + ev = readl(PRCM_ACK_MB0_WAKEUP_1_DBB); + else + ev = readl(PRCM_ACK_MB0_WAKEUP_0_DBB); + + ev &= mb0_transfer.req.dbb_irqs; + + for (n = 0; n < NUM_DB5500_PRCMU_WAKEUPS; n++) { + if (ev & prcmu_irq_bit[n]) + generic_handle_irq(IRQ_DB5500_PRCMU_BASE + n); + } r = true; break; default: @@ -315,31 +1652,119 @@ static bool read_mailbox_0(void) r = false; break; } - writel(MBOX_BIT(0), PRCM_ARM_IT1_CLEAR); + writel(MBOX_BIT(0), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); return r; } static bool read_mailbox_1(void) { - writel(MBOX_BIT(1), PRCM_ARM_IT1_CLEAR); + u8 header; + bool do_complete = true; + + header = mb1_transfer.ack.header = readb(PRCM_ACK_MB1_HEADER); + + switch (header) { + case MB1H_ARM_OPP: + mb1_transfer.ack.arm_opp = readb(PRCM_ACK_MB1_CURRENT_ARM_OPP); + mb1_transfer.ack.arm_voltage_st = + readb(PRCM_ACK_MB1_ARM_VOLT_STATUS); + break; + case MB1H_APE_OPP: + mb1_transfer.ack.ape_opp = readb(PRCM_ACK_MB1_CURRENT_APE_OPP); + mb1_transfer.ack.ape_voltage_st = + readb(PRCM_ACK_MB1_APE_VOLT_STATUS); + break; + case MB1H_ARM_APE_OPP: + mb1_transfer.ack.ape_opp = readb(PRCM_ACK_MB1_CURRENT_APE_OPP); + mb1_transfer.ack.ape_voltage_st = + readb(PRCM_ACK_MB1_APE_VOLT_STATUS); + break; + default: + print_unknown_header_warning(1, header); + do_complete = false; + break; + } + + writel(MBOX_BIT(1), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + + if (do_complete) + complete(&mb1_transfer.work); + return false; } static bool read_mailbox_2(void) { - writel(MBOX_BIT(2), PRCM_ARM_IT1_CLEAR); + u8 header; + + header = readb(PRCM_ACK_MB2_HEADER); + mb2_transfer.ack.header = header; + switch (header) { + case MB2H_EPOD_REQUEST: + mb2_transfer.ack.status = readb(PRCM_ACK_MB2_EPOD_STATUS); + break; + case MB2H_CLK_REQUEST: + mb2_transfer.ack.status = readb(PRCM_ACK_MB2_CLK_STATUS); + break; + case MB2H_PLL_REQUEST: + mb2_transfer.ack.status = readb(PRCM_ACK_MB2_PLL_STATUS); + break; + default: + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + pr_err("prcmu: Wrong ACK received for MB2 request \n"); + return false; + break; + } + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + complete(&mb2_transfer.work); return false; } static bool read_mailbox_3(void) { - writel(MBOX_BIT(3), PRCM_ARM_IT1_CLEAR); + u8 header; + + header = readb(PRCM_ACK_MB3_HEADER); + mb3_transfer.ack.header = header; + switch (header) { + case MB3H_REFCLK_REQUEST: + mb3_transfer.ack.status = readb(PRCM_ACK_MB3_REFCLK_REQ); + writel(MBOX_BIT(3), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + complete(&mb3_transfer.sysclk_work); + break; + default: + writel(MBOX_BIT(3), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + pr_err("prcmu: wrong MB3 header\n"); + break; + } + return false; } static bool read_mailbox_4(void) { - writel(MBOX_BIT(4), PRCM_ARM_IT1_CLEAR); + u8 header; + bool do_complete = true; + + header = readb(PRCM_ACK_MB4_HEADER); + mb4_transfer.ack.header = header; + switch (header) { + case MB4H_ACK_CFG_HOTDOG: + case MB4H_ACK_CFG_HOTMON: + case MB4H_ACK_CFG_HOTPERIOD: + mb4_transfer.ack.status = readb(PRCM_ACK_MB4_REQUESTS); + break; + default: + print_unknown_header_warning(4, header); + do_complete = false; + break; + } + + writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_ARM_IT1_CLEAR)); + + if (do_complete) + complete(&mb4_transfer.work); + return false; } @@ -360,19 +1785,19 @@ static bool read_mailbox_5(void) print_unknown_header_warning(5, header); break; } - writel(MBOX_BIT(5), PRCM_ARM_IT1_CLEAR); + writel(MBOX_BIT(5), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); return false; } static bool read_mailbox_6(void) { - writel(MBOX_BIT(6), PRCM_ARM_IT1_CLEAR); + writel(MBOX_BIT(6), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); return false; } static bool read_mailbox_7(void) { - writel(MBOX_BIT(7), PRCM_ARM_IT1_CLEAR); + writel(MBOX_BIT(7), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); return false; } @@ -393,7 +1818,7 @@ static irqreturn_t prcmu_irq_handler(int irq, void *data) u8 n; irqreturn_t r; - bits = (readl(PRCM_ARM_IT1_VAL) & ALL_MBOX_BITS); + bits = (readl(_PRCMU_BASE + PRCM_ARM_IT1_VAL) & ALL_MBOX_BITS); if (unlikely(!bits)) return IRQ_NONE; @@ -414,35 +1839,232 @@ static irqreturn_t prcmu_irq_thread_fn(int irq, void *data) return IRQ_HANDLED; } +static void prcmu_mask_work(struct work_struct *work) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + config_wakeups(); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +static void prcmu_irq_mask(struct irq_data *d) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.dbb_irqs_lock, flags); + + mb0_transfer.req.dbb_irqs &= ~prcmu_irq_bit[d->irq - IRQ_DB5500_PRCMU_BASE]; + + spin_unlock_irqrestore(&mb0_transfer.dbb_irqs_lock, flags); + schedule_work(&mb0_transfer.mask_work); +} + +static void prcmu_irq_unmask(struct irq_data *d) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.dbb_irqs_lock, flags); + + mb0_transfer.req.dbb_irqs |= prcmu_irq_bit[d->irq - IRQ_DB5500_PRCMU_BASE]; + + spin_unlock_irqrestore(&mb0_transfer.dbb_irqs_lock, flags); + schedule_work(&mb0_transfer.mask_work); +} + +static void noop(struct irq_data *d) +{ +} + +static struct irq_chip prcmu_irq_chip = { + .name = "prcmu", + .irq_disable = prcmu_irq_mask, + .irq_ack = noop, + .irq_mask = prcmu_irq_mask, + .irq_unmask = prcmu_irq_unmask, +}; + void __init db5500_prcmu_early_init(void) { + unsigned int i; + void *tcpm_base = ioremap_nocache(U5500_PRCMU_TCPM_BASE, SZ_4K); + + if (tcpm_base != NULL) { + int version_high, version_low; + + version_high = readl(tcpm_base + PRCMU_FW_VERSION_OFFSET); + version_low = readl(tcpm_base + PRCMU_FW_VERSION_OFFSET + 4); + prcmu_version.board = (version_high >> 24) & 0xFF; + prcmu_version.fw_version = version_high & 0xFF; + prcmu_version.api_version = version_low & 0xFF; + + pr_info("PRCMU Firmware Version: 0x%x\n", + prcmu_version.fw_version); + pr_info("PRCMU API Version: 0x%x\n", + prcmu_version.api_version); + + iounmap(tcpm_base); + } + tcdm_base = __io_address(U5500_PRCMU_TCDM_BASE); spin_lock_init(&mb0_transfer.lock); + spin_lock_init(&mb0_transfer.dbb_irqs_lock); + mutex_init(&mb1_transfer.lock); + init_completion(&mb1_transfer.work); + mutex_init(&mb2_transfer.lock); + init_completion(&mb2_transfer.work); + mutex_init(&mb3_transfer.sysclk_lock); + init_completion(&mb3_transfer.sysclk_work); + mutex_init(&mb4_transfer.lock); + init_completion(&mb4_transfer.work); mutex_init(&mb5_transfer.lock); init_completion(&mb5_transfer.work); + + INIT_WORK(&mb0_transfer.mask_work, prcmu_mask_work); + + /* Initalize irqs. */ + for (i = 0; i < NUM_DB5500_PRCMU_WAKEUPS; i++) { + unsigned int irq; + + irq = IRQ_DB5500_PRCMU_BASE + i; + irq_set_chip_and_handler(irq, &prcmu_irq_chip, + handle_simple_irq); + set_irq_flags(irq, IRQF_VALID); + } + prcmu_ape_clocks_init(); } +/* + * Power domain switches (ePODs) modeled as regulators for the DB5500 SoC + */ +static struct regulator_consumer_supply db5500_vape_consumers[] = { + REGULATOR_SUPPLY("v-ape", NULL), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.0"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.1"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.2"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.3"), + REGULATOR_SUPPLY("vcore", "sdi0"), + REGULATOR_SUPPLY("vcore", "sdi1"), + REGULATOR_SUPPLY("vcore", "sdi2"), + REGULATOR_SUPPLY("vcore", "sdi3"), + REGULATOR_SUPPLY("vcore", "sdi4"), + REGULATOR_SUPPLY("v-uart", "uart0"), + REGULATOR_SUPPLY("v-uart", "uart1"), + REGULATOR_SUPPLY("v-uart", "uart2"), + REGULATOR_SUPPLY("v-uart", "uart3"), + REGULATOR_SUPPLY("v-ape", "db5500-keypad"), +}; + +static struct regulator_consumer_supply db5500_sga_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.0"), + REGULATOR_SUPPLY("v-mali", NULL), +}; + +static struct regulator_consumer_supply db5500_hva_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.1"), + REGULATOR_SUPPLY("v-hva", NULL), +}; + +static struct regulator_consumer_supply db5500_sia_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.2"), + REGULATOR_SUPPLY("v-sia", "mmio_camera"), +}; + +static struct regulator_consumer_supply db5500_disp_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.3"), + REGULATOR_SUPPLY("vsupply", "b2r2_bus"), + REGULATOR_SUPPLY("vsupply", "mcde"), +}; + +static struct regulator_consumer_supply db5500_esram12_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.4"), + REGULATOR_SUPPLY("v-esram12", "mcde"), + REGULATOR_SUPPLY("esram12", "hva"), +}; + +#define DB5500_REGULATOR_SWITCH(lower, upper) \ +[DB5500_REGULATOR_SWITCH_##upper] = { \ + .constraints = { \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + }, \ + .consumer_supplies = db5500_##lower##_consumers, \ + .num_consumer_supplies = ARRAY_SIZE(db5500_##lower##_consumers),\ +} + +static struct regulator_init_data db5500_regulators[DB5500_NUM_REGULATORS] = { + [DB5500_REGULATOR_VAPE] = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db5500_vape_consumers, + .num_consumer_supplies = ARRAY_SIZE(db5500_vape_consumers), + }, + DB5500_REGULATOR_SWITCH(sga, SGA), + DB5500_REGULATOR_SWITCH(hva, HVA), + DB5500_REGULATOR_SWITCH(sia, SIA), + DB5500_REGULATOR_SWITCH(disp, DISP), + DB5500_REGULATOR_SWITCH(esram12, ESRAM12), +}; + +static struct mfd_cell db5500_prcmu_devs[] = { + { + .name = "db5500-prcmu-regulators", + .platform_data = &db5500_regulators, + .pdata_size = sizeof(db5500_regulators), + }, + { + .name = "cpufreq-u5500", + }, +}; + /** * prcmu_fw_init - arch init call for the Linux PRCMU fw init logic * */ -int __init db5500_prcmu_init(void) +static int __init db5500_prcmu_probe(struct platform_device *pdev) { - int r = 0; + int err = 0; if (ux500_is_svp() || !cpu_is_u5500()) return -ENODEV; /* Clean up the mailbox interrupts after pre-kernel code. */ - writel(ALL_MBOX_BITS, PRCM_ARM_IT1_CLEAR); + writel(ALL_MBOX_BITS, _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); - r = request_threaded_irq(IRQ_DB5500_PRCMU1, prcmu_irq_handler, - prcmu_irq_thread_fn, 0, "prcmu", NULL); - if (r < 0) { + err = request_threaded_irq(IRQ_DB5500_PRCMU1, prcmu_irq_handler, + prcmu_irq_thread_fn, IRQF_NO_SUSPEND, "prcmu", NULL); + if (err < 0) { pr_err("prcmu: Failed to allocate IRQ_DB5500_PRCMU1.\n"); - return -EBUSY; + err = -EBUSY; + goto no_irq_return; } - return 0; + + err = mfd_add_devices(&pdev->dev, 0, db5500_prcmu_devs, + ARRAY_SIZE(db5500_prcmu_devs), NULL, + 0); + + if (err) + pr_err("prcmu: Failed to add subdevices\n"); + else + pr_info("DB5500 PRCMU initialized\n"); + +no_irq_return: + return err; + +} + +static struct platform_driver db5500_prcmu_driver = { + .driver = { + .name = "db5500-prcmu", + .owner = THIS_MODULE, + }, +}; + +static int __init db5500_prcmu_init(void) +{ + return platform_driver_probe(&db5500_prcmu_driver, db5500_prcmu_probe); } arch_initcall(db5500_prcmu_init); diff --git a/drivers/mfd/db8500-prcmu-regs.h b/drivers/mfd/db8500-prcmu-regs.h deleted file mode 100644 index 3bbf04d5804..00000000000 --- a/drivers/mfd/db8500-prcmu-regs.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) STMicroelectronics 2009 - * Copyright (C) ST-Ericsson SA 2010 - * - * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> - * Author: Sundar Iyer <sundar.iyer@stericsson.com> - * - * License Terms: GNU General Public License v2 - * - * PRCM Unit registers - */ -#ifndef __DB8500_PRCMU_REGS_H -#define __DB8500_PRCMU_REGS_H - -#include <linux/bitops.h> -#include <mach/hardware.h> - -#define BITS(_start, _end) ((BIT(_end) - BIT(_start)) + BIT(_end)) - -#define PRCM_ARM_PLLDIVPS 0x118 -#define PRCM_ARM_PLLDIVPS_ARM_BRM_RATE BITS(0, 5) -#define PRCM_ARM_PLLDIVPS_MAX_MASK 0xF - -#define PRCM_PLLARM_LOCKP 0x0A8 -#define PRCM_PLLARM_LOCKP_PRCM_PLLARM_LOCKP3 BIT(1) - -#define PRCM_ARM_CHGCLKREQ 0x114 -#define PRCM_ARM_CHGCLKREQ_PRCM_ARM_CHGCLKREQ BIT(0) - -#define PRCM_PLLARM_ENABLE 0x98 -#define PRCM_PLLARM_ENABLE_PRCM_PLLARM_ENABLE BIT(0) -#define PRCM_PLLARM_ENABLE_PRCM_PLLARM_COUNTON BIT(8) - -#define PRCM_ARMCLKFIX_MGT 0x0 -#define PRCM_A9_RESETN_CLR 0x1f4 -#define PRCM_A9_RESETN_SET 0x1f0 -#define PRCM_ARM_LS_CLAMP 0x30C -#define PRCM_SRAM_A9 0x308 - -/* ARM WFI Standby signal register */ -#define PRCM_ARM_WFI_STANDBY 0x130 -#define PRCM_IOCR 0x310 -#define PRCM_IOCR_IOFORCE BIT(0) - -/* CPU mailbox registers */ -#define PRCM_MBOX_CPU_VAL 0x0FC -#define PRCM_MBOX_CPU_SET 0x100 - -/* Dual A9 core interrupt management unit registers */ -#define PRCM_A9_MASK_REQ 0x328 -#define PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ BIT(0) - -#define PRCM_A9_MASK_ACK 0x32C -#define PRCM_ARMITMSK31TO0 0x11C -#define PRCM_ARMITMSK63TO32 0x120 -#define PRCM_ARMITMSK95TO64 0x124 -#define PRCM_ARMITMSK127TO96 0x128 -#define PRCM_POWER_STATE_VAL 0x25C -#define PRCM_ARMITVAL31TO0 0x260 -#define PRCM_ARMITVAL63TO32 0x264 -#define PRCM_ARMITVAL95TO64 0x268 -#define PRCM_ARMITVAL127TO96 0x26C - -#define PRCM_HOSTACCESS_REQ 0x334 -#define PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ BIT(0) - -#define PRCM_ARM_IT1_CLR 0x48C -#define PRCM_ARM_IT1_VAL 0x494 - -#define PRCM_ITSTATUS0 0x148 -#define PRCM_ITSTATUS1 0x150 -#define PRCM_ITSTATUS2 0x158 -#define PRCM_ITSTATUS3 0x160 -#define PRCM_ITSTATUS4 0x168 -#define PRCM_ITSTATUS5 0x484 -#define PRCM_ITCLEAR5 0x488 -#define PRCM_ARMIT_MASKXP70_IT 0x1018 - -/* System reset register */ -#define PRCM_APE_SOFTRST 0x228 - -/* Level shifter and clamp control registers */ -#define PRCM_MMIP_LS_CLAMP_SET 0x420 -#define PRCM_MMIP_LS_CLAMP_CLR 0x424 - -/* PRCMU HW semaphore */ -#define PRCM_SEM 0x400 -#define PRCM_SEM_PRCM_SEM BIT(0) - -/* PRCMU clock/PLL/reset registers */ -#define PRCM_PLLDSI_FREQ 0x500 -#define PRCM_PLLDSI_ENABLE 0x504 -#define PRCM_PLLDSI_LOCKP 0x508 -#define PRCM_DSI_PLLOUT_SEL 0x530 -#define PRCM_DSITVCLK_DIV 0x52C -#define PRCM_APE_RESETN_SET 0x1E4 -#define PRCM_APE_RESETN_CLR 0x1E8 - -#define PRCM_TCR 0x1C8 -#define PRCM_TCR_TENSEL_MASK BITS(0, 7) -#define PRCM_TCR_STOP_TIMERS BIT(16) -#define PRCM_TCR_DOZE_MODE BIT(17) - -#define PRCM_CLKOCR 0x1CC -#define PRCM_CLKOCR_CLKODIV0_SHIFT 0 -#define PRCM_CLKOCR_CLKODIV0_MASK BITS(0, 5) -#define PRCM_CLKOCR_CLKOSEL0_SHIFT 6 -#define PRCM_CLKOCR_CLKOSEL0_MASK BITS(6, 8) -#define PRCM_CLKOCR_CLKODIV1_SHIFT 16 -#define PRCM_CLKOCR_CLKODIV1_MASK BITS(16, 21) -#define PRCM_CLKOCR_CLKOSEL1_SHIFT 22 -#define PRCM_CLKOCR_CLKOSEL1_MASK BITS(22, 24) -#define PRCM_CLKOCR_CLK1TYPE BIT(28) - -#define PRCM_SGACLK_MGT 0x014 -#define PRCM_UARTCLK_MGT 0x018 -#define PRCM_MSP02CLK_MGT 0x01C -#define PRCM_MSP1CLK_MGT 0x288 -#define PRCM_I2CCLK_MGT 0x020 -#define PRCM_SDMMCCLK_MGT 0x024 -#define PRCM_SLIMCLK_MGT 0x028 -#define PRCM_PER1CLK_MGT 0x02C -#define PRCM_PER2CLK_MGT 0x030 -#define PRCM_PER3CLK_MGT 0x034 -#define PRCM_PER5CLK_MGT 0x038 -#define PRCM_PER6CLK_MGT 0x03C -#define PRCM_PER7CLK_MGT 0x040 -#define PRCM_LCDCLK_MGT 0x044 -#define PRCM_BMLCLK_MGT 0x04C -#define PRCM_HSITXCLK_MGT 0x050 -#define PRCM_HSIRXCLK_MGT 0x054 -#define PRCM_HDMICLK_MGT 0x058 -#define PRCM_APEATCLK_MGT 0x05C -#define PRCM_APETRACECLK_MGT 0x060 -#define PRCM_MCDECLK_MGT 0x064 -#define PRCM_IPI2CCLK_MGT 0x068 -#define PRCM_DSIALTCLK_MGT 0x06C -#define PRCM_DMACLK_MGT 0x074 -#define PRCM_B2R2CLK_MGT 0x078 -#define PRCM_TVCLK_MGT 0x07C -#define PRCM_UNIPROCLK_MGT 0x278 -#define PRCM_SSPCLK_MGT 0x280 -#define PRCM_RNGCLK_MGT 0x284 -#define PRCM_UICCCLK_MGT 0x27C - -#define PRCM_CLK_MGT_CLKPLLDIV_MASK BITS(0, 4) -#define PRCM_CLK_MGT_CLKPLLSW_MASK BITS(5, 7) -#define PRCM_CLK_MGT_CLKEN BIT(8) - -/* ePOD and memory power signal control registers */ -#define PRCM_EPOD_C_SET 0x410 -#define PRCM_SRAM_LS_SLEEP 0x304 - -/* Debug power control unit registers */ -#define PRCM_POWER_STATE_SET 0x254 - -/* Miscellaneous unit registers */ -#define PRCM_DSI_SW_RESET 0x324 -#define PRCM_GPIOCR 0x138 - -/* GPIOCR register */ -#define PRCM_GPIOCR_SPI2_SELECT BIT(23) - -#define PRCM_DDR_SUBSYS_APE_MINBW 0x438 - -#endif /* __DB8500_PRCMU_REGS_H */ diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index 02a15d7cb3b..c9ccb8fd72c 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -27,14 +27,16 @@ #include <linux/platform_device.h> #include <linux/uaccess.h> #include <linux/mfd/core.h> -#include <linux/mfd/db8500-prcmu.h> +#include <linux/mfd/dbx500-prcmu.h> #include <linux/regulator/db8500-prcmu.h> #include <linux/regulator/machine.h> #include <mach/hardware.h> #include <mach/irqs.h> #include <mach/db8500-regs.h> #include <mach/id.h> -#include "db8500-prcmu-regs.h" +#include <mach/prcmu-debug.h> + +#include "dbx500-prcmu-regs.h" /* Offset for the firmware version within the TCPM */ #define PRCMU_FW_VERSION_OFFSET 0xA4 @@ -131,12 +133,14 @@ #define MB1H_REQUEST_APE_OPP_100_VOLT 0x3 #define MB1H_RELEASE_APE_OPP_100_VOLT 0x4 #define MB1H_RELEASE_USB_WAKEUP 0x5 +#define MB1H_PLL_ON_OFF 0x6 /* Mailbox 1 Requests */ #define PRCM_REQ_MB1_ARM_OPP (PRCM_REQ_MB1 + 0x0) #define PRCM_REQ_MB1_APE_OPP (PRCM_REQ_MB1 + 0x1) -#define PRCM_REQ_MB1_APE_OPP_100_RESTORE (PRCM_REQ_MB1 + 0x4) -#define PRCM_REQ_MB1_ARM_OPP_100_RESTORE (PRCM_REQ_MB1 + 0x8) +#define PRCM_REQ_MB1_PLL_ON_OFF (PRCM_REQ_MB1 + 0x4) +#define PLL_SOC1_OFF 0x4 +#define PLL_SOC1_ON 0x8 /* Mailbox 1 ACKs */ #define PRCM_ACK_MB1_CURRENT_ARM_OPP (PRCM_ACK_MB1 + 0x0) @@ -184,6 +188,11 @@ #define MB4H_HOTDOG 0x12 #define MB4H_HOTMON 0x13 #define MB4H_HOT_PERIOD 0x14 +#define MB4H_A9WDOG_CONF 0x16 +#define MB4H_A9WDOG_EN 0x17 +#define MB4H_A9WDOG_DIS 0x18 +#define MB4H_A9WDOG_LOAD 0x19 +#define MB4H_A9WDOG_KICK 0x20 /* Mailbox 4 Requests */ #define PRCM_REQ_MB4_DDR_ST_AP_SLEEP_IDLE (PRCM_REQ_MB4 + 0x0) @@ -196,6 +205,13 @@ #define PRCM_REQ_MB4_HOT_PERIOD (PRCM_REQ_MB4 + 0x0) #define HOTMON_CONFIG_LOW BIT(0) #define HOTMON_CONFIG_HIGH BIT(1) +#define PRCM_REQ_MB4_A9WDOG_0 (PRCM_REQ_MB4 + 0x0) +#define PRCM_REQ_MB4_A9WDOG_1 (PRCM_REQ_MB4 + 0x1) +#define PRCM_REQ_MB4_A9WDOG_2 (PRCM_REQ_MB4 + 0x2) +#define PRCM_REQ_MB4_A9WDOG_3 (PRCM_REQ_MB4 + 0x3) +#define A9WDOG_AUTO_OFF_EN BIT(7) +#define A9WDOG_AUTO_OFF_DIS 0 +#define A9WDOG_ID_MASK 0xf /* Mailbox 5 Requests */ #define PRCM_REQ_MB5_I2C_SLAVE_OP (PRCM_REQ_MB5 + 0x0) @@ -327,11 +343,13 @@ static struct { * mb1_transfer - state needed for mailbox 1 communication. * @lock: The transaction lock. * @work: The transaction completion structure. + * @ape_opp: The current APE OPP. * @ack: Reply ("acknowledge") data. */ static struct { struct mutex lock; struct completion work; + u8 ape_opp; struct { u8 header; u8 arm_opp; @@ -406,43 +424,81 @@ static DEFINE_SPINLOCK(gpiocr_lock); static __iomem void *tcdm_base; struct clk_mgt { - unsigned int offset; + void __iomem *reg; u32 pllsw; + int branch; + bool clk38div; +}; + +enum { + PLL_RAW, + PLL_FIX, + PLL_DIV }; static DEFINE_SPINLOCK(clk_mgt_lock); -#define CLK_MGT_ENTRY(_name)[PRCMU_##_name] = { (PRCM_##_name##_MGT), 0 } +#define CLK_MGT_ENTRY(_name, _branch, _clk38div)[PRCMU_##_name] = \ + { (PRCM_##_name##_MGT), 0 , _branch, _clk38div} struct clk_mgt clk_mgt[PRCMU_NUM_REG_CLOCKS] = { - CLK_MGT_ENTRY(SGACLK), - CLK_MGT_ENTRY(UARTCLK), - CLK_MGT_ENTRY(MSP02CLK), - CLK_MGT_ENTRY(MSP1CLK), - CLK_MGT_ENTRY(I2CCLK), - CLK_MGT_ENTRY(SDMMCCLK), - CLK_MGT_ENTRY(SLIMCLK), - CLK_MGT_ENTRY(PER1CLK), - CLK_MGT_ENTRY(PER2CLK), - CLK_MGT_ENTRY(PER3CLK), - CLK_MGT_ENTRY(PER5CLK), - CLK_MGT_ENTRY(PER6CLK), - CLK_MGT_ENTRY(PER7CLK), - CLK_MGT_ENTRY(LCDCLK), - CLK_MGT_ENTRY(BMLCLK), - CLK_MGT_ENTRY(HSITXCLK), - CLK_MGT_ENTRY(HSIRXCLK), - CLK_MGT_ENTRY(HDMICLK), - CLK_MGT_ENTRY(APEATCLK), - CLK_MGT_ENTRY(APETRACECLK), - CLK_MGT_ENTRY(MCDECLK), - CLK_MGT_ENTRY(IPI2CCLK), - CLK_MGT_ENTRY(DSIALTCLK), - CLK_MGT_ENTRY(DMACLK), - CLK_MGT_ENTRY(B2R2CLK), - CLK_MGT_ENTRY(TVCLK), - CLK_MGT_ENTRY(SSPCLK), - CLK_MGT_ENTRY(RNGCLK), - CLK_MGT_ENTRY(UICCCLK), + CLK_MGT_ENTRY(SGACLK, PLL_DIV, false), + CLK_MGT_ENTRY(UARTCLK, PLL_FIX, true), + CLK_MGT_ENTRY(MSP02CLK, PLL_FIX, true), + CLK_MGT_ENTRY(MSP1CLK, PLL_FIX, true), + CLK_MGT_ENTRY(I2CCLK, PLL_FIX, true), + CLK_MGT_ENTRY(SDMMCCLK, PLL_DIV, true), + CLK_MGT_ENTRY(SLIMCLK, PLL_FIX, true), + CLK_MGT_ENTRY(PER1CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER2CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER3CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER5CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER6CLK, PLL_DIV, true), + CLK_MGT_ENTRY(PER7CLK, PLL_DIV, true), + CLK_MGT_ENTRY(LCDCLK, PLL_FIX, true), + CLK_MGT_ENTRY(BMLCLK, PLL_DIV, true), + CLK_MGT_ENTRY(HSITXCLK, PLL_DIV, true), + CLK_MGT_ENTRY(HSIRXCLK, PLL_DIV, true), + CLK_MGT_ENTRY(HDMICLK, PLL_FIX, false), + CLK_MGT_ENTRY(APEATCLK, PLL_DIV, true), + CLK_MGT_ENTRY(APETRACECLK, PLL_DIV, true), + CLK_MGT_ENTRY(MCDECLK, PLL_DIV, true), + CLK_MGT_ENTRY(IPI2CCLK, PLL_FIX, true), + CLK_MGT_ENTRY(DSIALTCLK, PLL_FIX, false), + CLK_MGT_ENTRY(DMACLK, PLL_DIV, true), + CLK_MGT_ENTRY(B2R2CLK, PLL_DIV, true), + CLK_MGT_ENTRY(TVCLK, PLL_FIX, false), + CLK_MGT_ENTRY(SSPCLK, PLL_FIX, true), + CLK_MGT_ENTRY(RNGCLK, PLL_FIX, true), + CLK_MGT_ENTRY(UICCCLK, PLL_FIX, false), +}; + +static struct regulator *hwacc_regulator[NUM_HW_ACC]; +static struct regulator *hwacc_ret_regulator[NUM_HW_ACC]; + +static bool hwacc_enabled[NUM_HW_ACC]; +static bool hwacc_ret_enabled[NUM_HW_ACC]; + +static const char *hwacc_regulator_name[NUM_HW_ACC] = { + [HW_ACC_SVAMMDSP] = "hwacc-sva-mmdsp", + [HW_ACC_SVAPIPE] = "hwacc-sva-pipe", + [HW_ACC_SIAMMDSP] = "hwacc-sia-mmdsp", + [HW_ACC_SIAPIPE] = "hwacc-sia-pipe", + [HW_ACC_SGA] = "hwacc-sga", + [HW_ACC_B2R2] = "hwacc-b2r2", + [HW_ACC_MCDE] = "hwacc-mcde", + [HW_ACC_ESRAM1] = "hwacc-esram1", + [HW_ACC_ESRAM2] = "hwacc-esram2", + [HW_ACC_ESRAM3] = "hwacc-esram3", + [HW_ACC_ESRAM4] = "hwacc-esram4", +}; + +static const char *hwacc_ret_regulator_name[NUM_HW_ACC] = { + [HW_ACC_SVAMMDSP] = "hwacc-sva-mmdsp-ret", + [HW_ACC_SIAMMDSP] = "hwacc-sia-mmdsp-ret", + [HW_ACC_ESRAM1] = "hwacc-esram1-ret", + [HW_ACC_ESRAM2] = "hwacc-esram2-ret", + [HW_ACC_ESRAM3] = "hwacc-esram3-ret", + [HW_ACC_ESRAM4] = "hwacc-esram4-ret", }; /* @@ -493,55 +549,51 @@ static struct { } prcmu_version; -int prcmu_enable_dsipll(void) +int db8500_prcmu_enable_dsipll(void) { int i; unsigned int plldsifreq; /* Clear DSIPLL_RESETN */ - writel(PRCMU_RESET_DSIPLL, (_PRCMU_BASE + PRCM_APE_RESETN_CLR)); + writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_CLR); /* Unclamp DSIPLL in/out */ - writel(PRCMU_UNCLAMP_DSIPLL, (_PRCMU_BASE + PRCM_MMIP_LS_CLAMP_CLR)); + writel(PRCMU_UNCLAMP_DSIPLL, PRCM_MMIP_LS_CLAMP_CLR); if (prcmu_is_u8400()) plldsifreq = PRCMU_PLLDSI_FREQ_SETTING_U8400; else plldsifreq = PRCMU_PLLDSI_FREQ_SETTING; /* Set DSI PLL FREQ */ - writel(plldsifreq, (_PRCMU_BASE + PRCM_PLLDSI_FREQ)); - writel(PRCMU_DSI_PLLOUT_SEL_SETTING, - (_PRCMU_BASE + PRCM_DSI_PLLOUT_SEL)); + writel(plldsifreq, PRCM_PLLDSI_FREQ); + writel(PRCMU_DSI_PLLOUT_SEL_SETTING, PRCM_DSI_PLLOUT_SEL); /* Enable Escape clocks */ - writel(PRCMU_ENABLE_ESCAPE_CLOCK_DIV, - (_PRCMU_BASE + PRCM_DSITVCLK_DIV)); + writel(PRCMU_ENABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); /* Start DSI PLL */ - writel(PRCMU_ENABLE_PLLDSI, (_PRCMU_BASE + PRCM_PLLDSI_ENABLE)); + writel(PRCMU_ENABLE_PLLDSI, PRCM_PLLDSI_ENABLE); /* Reset DSI PLL */ - writel(PRCMU_DSI_RESET_SW, (_PRCMU_BASE + PRCM_DSI_SW_RESET)); + writel(PRCMU_DSI_RESET_SW, PRCM_DSI_SW_RESET); for (i = 0; i < 10; i++) { - if ((readl(_PRCMU_BASE + PRCM_PLLDSI_LOCKP) & - PRCMU_PLLDSI_LOCKP_LOCKED) + if ((readl(PRCM_PLLDSI_LOCKP) & PRCMU_PLLDSI_LOCKP_LOCKED) == PRCMU_PLLDSI_LOCKP_LOCKED) break; udelay(100); } /* Set DSIPLL_RESETN */ - writel(PRCMU_RESET_DSIPLL, (_PRCMU_BASE + PRCM_APE_RESETN_SET)); + writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_SET); return 0; } -int prcmu_disable_dsipll(void) +int db8500_prcmu_disable_dsipll(void) { /* Disable dsi pll */ - writel(PRCMU_DISABLE_PLLDSI, (_PRCMU_BASE + PRCM_PLLDSI_ENABLE)); + writel(PRCMU_DISABLE_PLLDSI, PRCM_PLLDSI_ENABLE); /* Disable escapeclock */ - writel(PRCMU_DISABLE_ESCAPE_CLOCK_DIV, - (_PRCMU_BASE + PRCM_DSITVCLK_DIV)); + writel(PRCMU_DISABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); return 0; } -int prcmu_set_display_clocks(void) +int db8500_prcmu_set_display_clocks(void) { unsigned long flags; unsigned int dsiclk; @@ -554,15 +606,15 @@ int prcmu_set_display_clocks(void) spin_lock_irqsave(&clk_mgt_lock, flags); /* Grab the HW semaphore. */ - while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) cpu_relax(); - writel(dsiclk, (_PRCMU_BASE + PRCM_HDMICLK_MGT)); - writel(PRCMU_DSI_LP_CLOCK_SETTING, (_PRCMU_BASE + PRCM_TVCLK_MGT)); - writel(PRCMU_DPI_CLOCK_SETTING, (_PRCMU_BASE + PRCM_LCDCLK_MGT)); + writel(dsiclk, PRCM_HDMICLK_MGT); + writel(PRCMU_DSI_LP_CLOCK_SETTING, PRCM_TVCLK_MGT); + writel(PRCMU_DPI_CLOCK_SETTING, PRCM_LCDCLK_MGT); /* Release the HW semaphore. */ - writel(0, (_PRCMU_BASE + PRCM_SEM)); + writel(0, PRCM_SEM); spin_unlock_irqrestore(&clk_mgt_lock, flags); @@ -578,8 +630,8 @@ void prcmu_enable_spi2(void) unsigned long flags; spin_lock_irqsave(&gpiocr_lock, flags); - reg = readl(_PRCMU_BASE + PRCM_GPIOCR); - writel(reg | PRCM_GPIOCR_SPI2_SELECT, _PRCMU_BASE + PRCM_GPIOCR); + reg = readl(PRCM_GPIOCR); + writel(reg | PRCM_GPIOCR_SPI2_SELECT, PRCM_GPIOCR); spin_unlock_irqrestore(&gpiocr_lock, flags); } @@ -592,8 +644,8 @@ void prcmu_disable_spi2(void) unsigned long flags; spin_lock_irqsave(&gpiocr_lock, flags); - reg = readl(_PRCMU_BASE + PRCM_GPIOCR); - writel(reg & ~PRCM_GPIOCR_SPI2_SELECT, _PRCMU_BASE + PRCM_GPIOCR); + reg = readl(PRCM_GPIOCR); + writel(reg & ~PRCM_GPIOCR_SPI2_SELECT, PRCM_GPIOCR); spin_unlock_irqrestore(&gpiocr_lock, flags); } @@ -701,7 +753,7 @@ int prcmu_config_clkout(u8 clkout, u8 source, u8 div) spin_lock_irqsave(&clkout_lock, flags); - val = readl(_PRCMU_BASE + PRCM_CLKOCR); + val = readl(PRCM_CLKOCR); if (val & div_mask) { if (div) { if ((val & mask) != bits) { @@ -715,7 +767,7 @@ int prcmu_config_clkout(u8 clkout, u8 source, u8 div) } } } - writel((bits | (val & ~mask)), (_PRCMU_BASE + PRCM_CLKOCR)); + writel((bits | (val & ~mask)), PRCM_CLKOCR); requests[clkout] += (div ? 1 : -1); unlock_and_return: @@ -724,7 +776,7 @@ unlock_and_return: return r; } -int prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) +int db8500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) { unsigned long flags; @@ -732,7 +784,7 @@ int prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) spin_lock_irqsave(&mb0_transfer.lock, flags); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) cpu_relax(); writeb(MB0H_POWER_STATE_TRANS, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB0)); @@ -741,7 +793,7 @@ int prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) writeb((keep_ulp_clk ? 1 : 0), (tcdm_base + PRCM_REQ_MB0_ULP_CLOCK_STATE)); writeb(0, (tcdm_base + PRCM_REQ_MB0_DO_NOT_WFI)); - writel(MBOX_BIT(0), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(0), PRCM_MBOX_CPU_SET); spin_unlock_irqrestore(&mb0_transfer.lock, flags); @@ -770,18 +822,18 @@ static void config_wakeups(void) return; for (i = 0; i < 2; i++) { - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) cpu_relax(); writel(dbb_events, (tcdm_base + PRCM_REQ_MB0_WAKEUP_8500)); writel(abb_events, (tcdm_base + PRCM_REQ_MB0_WAKEUP_4500)); writeb(header[i], (tcdm_base + PRCM_MBOX_HEADER_REQ_MB0)); - writel(MBOX_BIT(0), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(0), PRCM_MBOX_CPU_SET); } last_dbb_events = dbb_events; last_abb_events = abb_events; } -void prcmu_enable_wakeups(u32 wakeups) +void db8500_prcmu_enable_wakeups(u32 wakeups) { unsigned long flags; u32 bits; @@ -802,7 +854,7 @@ void prcmu_enable_wakeups(u32 wakeups) spin_unlock_irqrestore(&mb0_transfer.lock, flags); } -void prcmu_config_abb_event_readout(u32 abb_events) +void db8500_prcmu_config_abb_event_readout(u32 abb_events) { unsigned long flags; @@ -814,7 +866,7 @@ void prcmu_config_abb_event_readout(u32 abb_events) spin_unlock_irqrestore(&mb0_transfer.lock, flags); } -void prcmu_get_abb_event_buffer(void __iomem **buf) +void db8500_prcmu_get_abb_event_buffer(void __iomem **buf) { if (readb(tcdm_base + PRCM_ACK_MB0_READ_POINTER) & 1) *buf = (tcdm_base + PRCM_ACK_MB0_WAKEUP_1_4500); @@ -823,13 +875,13 @@ void prcmu_get_abb_event_buffer(void __iomem **buf) } /** - * prcmu_set_arm_opp - set the appropriate ARM OPP + * db8500_prcmu_set_arm_opp - set the appropriate ARM OPP * @opp: The new ARM operating point to which transition is to be made * Returns: 0 on success, non-zero on failure * * This function sets the the operating point of the ARM. */ -int prcmu_set_arm_opp(u8 opp) +int db8500_prcmu_set_arm_opp(u8 opp) { int r; @@ -840,14 +892,14 @@ int prcmu_set_arm_opp(u8 opp) mutex_lock(&mb1_transfer.lock); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) cpu_relax(); writeb(MB1H_ARM_APE_OPP, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); writeb(opp, (tcdm_base + PRCM_REQ_MB1_ARM_OPP)); writeb(APE_NO_CHANGE, (tcdm_base + PRCM_REQ_MB1_APE_OPP)); - writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); wait_for_completion(&mb1_transfer.work); if ((mb1_transfer.ack.header != MB1H_ARM_APE_OPP) || @@ -856,84 +908,150 @@ int prcmu_set_arm_opp(u8 opp) mutex_unlock(&mb1_transfer.lock); + prcmu_debug_arm_opp_log(opp); + return r; } /** - * prcmu_get_arm_opp - get the current ARM OPP + * db8500_prcmu_get_arm_opp - get the current ARM OPP * * Returns: the current ARM OPP */ -int prcmu_get_arm_opp(void) +int db8500_prcmu_get_arm_opp(void) { return readb(tcdm_base + PRCM_ACK_MB1_CURRENT_ARM_OPP); } /** - * prcmu_get_ddr_opp - get the current DDR OPP + * db8500_prcmu_get_ddr_opp - get the current DDR OPP * * Returns: the current DDR OPP */ -int prcmu_get_ddr_opp(void) +int db8500_prcmu_get_ddr_opp(void) { - return readb(_PRCMU_BASE + PRCM_DDR_SUBSYS_APE_MINBW); + return readb(PRCM_DDR_SUBSYS_APE_MINBW); } /** - * set_ddr_opp - set the appropriate DDR OPP + * db8500_set_ddr_opp - set the appropriate DDR OPP * @opp: The new DDR operating point to which transition is to be made * Returns: 0 on success, non-zero on failure * * This function sets the operating point of the DDR. */ -int prcmu_set_ddr_opp(u8 opp) +int db8500_prcmu_set_ddr_opp(u8 opp) { if (opp < DDR_100_OPP || opp > DDR_25_OPP) return -EINVAL; /* Changing the DDR OPP can hang the hardware pre-v21 */ if (cpu_is_u8500v20_or_later() && !cpu_is_u8500v20()) - writeb(opp, (_PRCMU_BASE + PRCM_DDR_SUBSYS_APE_MINBW)); + writeb(opp, PRCM_DDR_SUBSYS_APE_MINBW); return 0; } + +/* Divide the frequency of certain clocks by 2 for APE_50_PARTLY_25_OPP. */ +static void request_even_slower_clocks(bool enable) +{ + void __iomem *clock_reg[] = { + PRCM_ACLK_MGT, + PRCM_DMACLK_MGT + }; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + for (i = 0; i < ARRAY_SIZE(clock_reg); i++) { + u32 val; + u32 div; + + val = readl(clock_reg[i]); + div = (val & PRCM_CLK_MGT_CLKPLLDIV_MASK); + if (enable) { + if ((div <= 1) || (div > 15)) { + pr_err("prcmu: Bad clock divider %d in %s\n", + div, __func__); + goto unlock_and_return; + } + div <<= 1; + } else { + if (div <= 2) + goto unlock_and_return; + div >>= 1; + } + val = ((val & ~PRCM_CLK_MGT_CLKPLLDIV_MASK) | + (div & PRCM_CLK_MGT_CLKPLLDIV_MASK)); + writel(val, clock_reg[i]); + } + +unlock_and_return: + /* Release the HW semaphore. */ + writel(0, PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); +} + /** - * set_ape_opp - set the appropriate APE OPP + * db8500_set_ape_opp - set the appropriate APE OPP * @opp: The new APE operating point to which transition is to be made * Returns: 0 on success, non-zero on failure * * This function sets the operating point of the APE. */ -int prcmu_set_ape_opp(u8 opp) +int db8500_prcmu_set_ape_opp(u8 opp) { int r = 0; + if (opp == mb1_transfer.ape_opp) + return 0; + mutex_lock(&mb1_transfer.lock); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + if (mb1_transfer.ape_opp == APE_50_PARTLY_25_OPP) + request_even_slower_clocks(false); + + if ((opp != APE_100_OPP) && (mb1_transfer.ape_opp != APE_100_OPP)) + goto skip_message; + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) cpu_relax(); writeb(MB1H_ARM_APE_OPP, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); writeb(ARM_NO_CHANGE, (tcdm_base + PRCM_REQ_MB1_ARM_OPP)); - writeb(opp, (tcdm_base + PRCM_REQ_MB1_APE_OPP)); + writeb(((opp == APE_50_PARTLY_25_OPP) ? APE_50_OPP : opp), + (tcdm_base + PRCM_REQ_MB1_APE_OPP)); - writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); wait_for_completion(&mb1_transfer.work); if ((mb1_transfer.ack.header != MB1H_ARM_APE_OPP) || (mb1_transfer.ack.ape_opp != opp)) r = -EIO; +skip_message: + if ((!r && (opp == APE_50_PARTLY_25_OPP)) || + (r && (mb1_transfer.ape_opp == APE_50_PARTLY_25_OPP))) + request_even_slower_clocks(true); + if (!r) + mb1_transfer.ape_opp = opp; + mutex_unlock(&mb1_transfer.lock); return r; } /** - * prcmu_get_ape_opp - get the current APE OPP + * db8500_prcmu_get_ape_opp - get the current APE OPP * * Returns: the current APE OPP */ -int prcmu_get_ape_opp(void) +int db8500_prcmu_get_ape_opp(void) { return readb(tcdm_base + PRCM_ACK_MB1_CURRENT_APE_OPP); } @@ -966,12 +1084,12 @@ int prcmu_request_ape_opp_100_voltage(bool enable) header = MB1H_RELEASE_APE_OPP_100_VOLT; } - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) cpu_relax(); writeb(header, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); - writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); wait_for_completion(&mb1_transfer.work); if ((mb1_transfer.ack.header != header) || @@ -995,13 +1113,13 @@ int prcmu_release_usb_wakeup_state(void) mutex_lock(&mb1_transfer.lock); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) cpu_relax(); writeb(MB1H_RELEASE_USB_WAKEUP, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); - writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); wait_for_completion(&mb1_transfer.work); if ((mb1_transfer.ack.header != MB1H_RELEASE_USB_WAKEUP) || @@ -1013,15 +1131,169 @@ int prcmu_release_usb_wakeup_state(void) return r; } +static int request_pll(u8 clock, bool enable) +{ + int r = 0; + + if (clock == PRCMU_PLLSOC1) + clock = (enable ? PLL_SOC1_ON : PLL_SOC1_OFF); + else + return -EINVAL; + + mutex_lock(&mb1_transfer.lock); + + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_PLL_ON_OFF, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); + writeb(clock, (tcdm_base + PRCM_REQ_MB1_PLL_ON_OFF)); + + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb1_transfer.work); + + if (mb1_transfer.ack.header != MB1H_PLL_ON_OFF) + r = -EIO; + + mutex_unlock(&mb1_transfer.lock); + + return r; +} + +/** + * prcmu_set_hwacc - set the power state of a h/w accelerator + * @hwacc_dev: The hardware accelerator (enum hw_acc_dev). + * @state: The new power state (enum hw_acc_state). + * + * This function sets the power state of a hardware accelerator. + * This function should not be called from interrupt context. + * + * NOTE! Deprecated, to be removed when all users switched over to use the + * regulator framework API. + */ +int prcmu_set_hwacc(u16 hwacc_dev, u8 state) +{ + int r = 0; + bool ram_retention = false; + bool enable, enable_ret; + + /* check argument */ + BUG_ON(hwacc_dev >= NUM_HW_ACC); + + /* get state of switches */ + enable = hwacc_enabled[hwacc_dev]; + enable_ret = hwacc_ret_enabled[hwacc_dev]; + + /* set flag if retention is possible */ + switch (hwacc_dev) { + case HW_ACC_SVAMMDSP: + case HW_ACC_SIAMMDSP: + case HW_ACC_ESRAM1: + case HW_ACC_ESRAM2: + case HW_ACC_ESRAM3: + case HW_ACC_ESRAM4: + ram_retention = true; + break; + } + + /* check argument */ + BUG_ON(state > HW_ON); + BUG_ON(state == HW_OFF_RAMRET && !ram_retention); + + /* modify enable flags */ + switch (state) { + case HW_OFF: + enable_ret = false; + enable = false; + break; + case HW_ON: + enable = true; + break; + case HW_OFF_RAMRET: + enable_ret = true; + enable = false; + break; + } + + /* get regulator (lazy) */ + if (hwacc_regulator[hwacc_dev] == NULL) { + hwacc_regulator[hwacc_dev] = regulator_get(NULL, + hwacc_regulator_name[hwacc_dev]); + if (IS_ERR(hwacc_regulator[hwacc_dev])) { + pr_err("prcmu: failed to get supply %s\n", + hwacc_regulator_name[hwacc_dev]); + r = PTR_ERR(hwacc_regulator[hwacc_dev]); + goto out; + } + } + + if (ram_retention) { + if (hwacc_ret_regulator[hwacc_dev] == NULL) { + hwacc_ret_regulator[hwacc_dev] = regulator_get(NULL, + hwacc_ret_regulator_name[hwacc_dev]); + if (IS_ERR(hwacc_ret_regulator[hwacc_dev])) { + pr_err("prcmu: failed to get supply %s\n", + hwacc_ret_regulator_name[hwacc_dev]); + r = PTR_ERR(hwacc_ret_regulator[hwacc_dev]); + goto out; + } + } + } + + /* set regulators */ + if (ram_retention) { + if (enable_ret && !hwacc_ret_enabled[hwacc_dev]) { + r = regulator_enable(hwacc_ret_regulator[hwacc_dev]); + if (r < 0) { + pr_err("prcmu_set_hwacc: ret enable failed\n"); + goto out; + } + hwacc_ret_enabled[hwacc_dev] = true; + } + } + + if (enable && !hwacc_enabled[hwacc_dev]) { + r = regulator_enable(hwacc_regulator[hwacc_dev]); + if (r < 0) { + pr_err("prcmu_set_hwacc: enable failed\n"); + goto out; + } + hwacc_enabled[hwacc_dev] = true; + } + + if (!enable && hwacc_enabled[hwacc_dev]) { + r = regulator_disable(hwacc_regulator[hwacc_dev]); + if (r < 0) { + pr_err("prcmu_set_hwacc: disable failed\n"); + goto out; + } + hwacc_enabled[hwacc_dev] = false; + } + + if (ram_retention) { + if (!enable_ret && hwacc_ret_enabled[hwacc_dev]) { + r = regulator_disable(hwacc_ret_regulator[hwacc_dev]); + if (r < 0) { + pr_err("prcmu_set_hwacc: ret disable failed\n"); + goto out; + } + hwacc_ret_enabled[hwacc_dev] = false; + } + } + +out: + return r; +} +EXPORT_SYMBOL(prcmu_set_hwacc); + /** - * prcmu_set_epod - set the state of a EPOD (power domain) + * db8500_prcmu_set_epod - set the state of a EPOD (power domain) * @epod_id: The EPOD to set * @epod_state: The new EPOD state * * This function sets the state of a EPOD (power domain). It may not be called * from interrupt context. */ -int prcmu_set_epod(u16 epod_id, u8 epod_state) +int db8500_prcmu_set_epod(u16 epod_id, u8 epod_state) { int r = 0; bool ram_retention = false; @@ -1048,7 +1320,7 @@ int prcmu_set_epod(u16 epod_id, u8 epod_state) mutex_lock(&mb2_transfer.lock); /* wait for mailbox */ - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) cpu_relax(); /* fill in mailbox */ @@ -1058,7 +1330,7 @@ int prcmu_set_epod(u16 epod_id, u8 epod_state) writeb(MB2H_DPS, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB2)); - writel(MBOX_BIT(2), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(2), PRCM_MBOX_CPU_SET); /* * The current firmware version does not handle errors correctly, @@ -1145,13 +1417,13 @@ static int request_sysclk(bool enable) spin_lock_irqsave(&mb3_transfer.lock, flags); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(3)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(3)) cpu_relax(); writeb((enable ? ON : OFF), (tcdm_base + PRCM_REQ_MB3_SYSCLK_MGT)); writeb(MB3H_SYSCLK, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB3)); - writel(MBOX_BIT(3), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(3), PRCM_MBOX_CPU_SET); spin_unlock_irqrestore(&mb3_transfer.lock, flags); @@ -1177,12 +1449,12 @@ static int request_timclk(bool enable) if (!enable) val |= PRCM_TCR_STOP_TIMERS; - writel(val, (_PRCMU_BASE + PRCM_TCR)); + writel(val, PRCM_TCR); return 0; } -static int request_reg_clock(u8 clock, bool enable) +static int request_clock(u8 clock, bool enable) { u32 val; unsigned long flags; @@ -1190,47 +1462,289 @@ static int request_reg_clock(u8 clock, bool enable) spin_lock_irqsave(&clk_mgt_lock, flags); /* Grab the HW semaphore. */ - while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) cpu_relax(); - val = readl(_PRCMU_BASE + clk_mgt[clock].offset); + val = readl(clk_mgt[clock].reg); if (enable) { val |= (PRCM_CLK_MGT_CLKEN | clk_mgt[clock].pllsw); } else { clk_mgt[clock].pllsw = (val & PRCM_CLK_MGT_CLKPLLSW_MASK); val &= ~(PRCM_CLK_MGT_CLKEN | PRCM_CLK_MGT_CLKPLLSW_MASK); } - writel(val, (_PRCMU_BASE + clk_mgt[clock].offset)); + writel(val, clk_mgt[clock].reg); /* Release the HW semaphore. */ - writel(0, (_PRCMU_BASE + PRCM_SEM)); + writel(0, PRCM_SEM); spin_unlock_irqrestore(&clk_mgt_lock, flags); return 0; } +static int request_sga_clock(u8 clock, bool enable) +{ + u32 val; + int ret; + + if (enable) { + val = readl(PRCM_CGATING_BYPASS); + writel(val | PRCM_CGATING_BYPASS_ICN2, PRCM_CGATING_BYPASS); + } + + ret = request_clock(clock, enable); + + if (!ret && !enable) { + val = readl(PRCM_CGATING_BYPASS); + writel(val & ~PRCM_CGATING_BYPASS_ICN2, PRCM_CGATING_BYPASS); + } + + return ret; +} + /** - * prcmu_request_clock() - Request for a clock to be enabled or disabled. + * db8500_prcmu_request_clock() - Request for a clock to be enabled or disabled. * @clock: The clock for which the request is made. * @enable: Whether the clock should be enabled (true) or disabled (false). * * This function should only be used by the clock implementation. * Do not use it from any other place! */ -int prcmu_request_clock(u8 clock, bool enable) +int db8500_prcmu_request_clock(u8 clock, bool enable) { - if (clock < PRCMU_NUM_REG_CLOCKS) - return request_reg_clock(clock, enable); + if (clock == PRCMU_SGACLK) + return request_sga_clock(clock, enable); + else if (clock < PRCMU_NUM_REG_CLOCKS) + return request_clock(clock, enable); else if (clock == PRCMU_TIMCLK) return request_timclk(enable); else if (clock == PRCMU_SYSCLK) return request_sysclk(enable); + else if (clock == PRCMU_PLLSOC1) + return request_pll(clock, enable); else return -EINVAL; } -int prcmu_config_esram0_deep_sleep(u8 state) +static unsigned long pll_rate(void __iomem *reg, unsigned long src_rate, + int branch) +{ + u64 rate; + u32 val; + u32 d; + u32 div = 1; + + val = readl(reg); + + rate = src_rate; + rate *= ((val & PRCM_PLL_FREQ_D_MASK) >> PRCM_PLL_FREQ_D_SHIFT); + + d = ((val & PRCM_PLL_FREQ_N_MASK) >> PRCM_PLL_FREQ_N_SHIFT); + if (d > 1) + div *= d; + + d = ((val & PRCM_PLL_FREQ_R_MASK) >> PRCM_PLL_FREQ_R_SHIFT); + if (d > 1) + div *= d; + + if (val & PRCM_PLL_FREQ_SELDIV2) + div *= 2; + + if ((branch == PLL_FIX) || ((branch == PLL_DIV) && + (val & PRCM_PLL_FREQ_DIV2EN) && + ((reg == PRCM_PLLSOC0_FREQ) || + (reg == PRCM_PLLDDR_FREQ)))) + div *= 2; + + (void)do_div(rate, div); + + return (unsigned long)rate; +} + +#define ROOT_CLOCK_RATE 38400000 + +static unsigned long clock_rate(u8 clock) +{ + u32 val; + u32 pllsw; + unsigned long rate = ROOT_CLOCK_RATE; + + val = readl(clk_mgt[clock].reg); + + if (val & PRCM_CLK_MGT_CLK38) { + if (clk_mgt[clock].clk38div && (val & PRCM_CLK_MGT_CLK38DIV)) + rate /= 2; + return rate; + } + + val |= clk_mgt[clock].pllsw; + pllsw = (val & PRCM_CLK_MGT_CLKPLLSW_MASK); + + if (pllsw == PRCM_CLK_MGT_CLKPLLSW_SOC0) + rate = pll_rate(PRCM_PLLSOC0_FREQ, rate, clk_mgt[clock].branch); + else if (pllsw == PRCM_CLK_MGT_CLKPLLSW_SOC1) + rate = pll_rate(PRCM_PLLSOC1_FREQ, rate, clk_mgt[clock].branch); + else if (pllsw == PRCM_CLK_MGT_CLKPLLSW_DDR) + rate = pll_rate(PRCM_PLLDDR_FREQ, rate, clk_mgt[clock].branch); + else + return 0; + + if ((clock == PRCMU_SGACLK) && + (val & PRCM_SGACLK_MGT_SGACLKDIV_BY_2_5_EN)) { + u64 r = (rate * 10); + + (void)do_div(r, 25); + return (unsigned long)r; + } + val &= PRCM_CLK_MGT_CLKPLLDIV_MASK; + if (val) + return rate / val; + else + return 0; +} + +unsigned long prcmu_clock_rate(u8 clock) +{ + if (clock < PRCMU_NUM_REG_CLOCKS) + return clock_rate(clock); + else if (clock == PRCMU_TIMCLK) + return ROOT_CLOCK_RATE / 16; + else if (clock == PRCMU_SYSCLK) + return ROOT_CLOCK_RATE; + else if (clock == PRCMU_PLLSOC0) + return pll_rate(PRCM_PLLSOC0_FREQ, ROOT_CLOCK_RATE, PLL_RAW); + else if (clock == PRCMU_PLLSOC1) + return pll_rate(PRCM_PLLSOC1_FREQ, ROOT_CLOCK_RATE, PLL_RAW); + else if (clock == PRCMU_PLLDDR) + return pll_rate(PRCM_PLLDDR_FREQ, ROOT_CLOCK_RATE, PLL_RAW); + else + return 0; +} + +static unsigned long clock_source_rate(u32 clk_mgt_val, int branch) +{ + if (clk_mgt_val & PRCM_CLK_MGT_CLK38) + return ROOT_CLOCK_RATE; + clk_mgt_val &= PRCM_CLK_MGT_CLKPLLSW_MASK; + if (clk_mgt_val == PRCM_CLK_MGT_CLKPLLSW_SOC0) + return pll_rate(PRCM_PLLSOC0_FREQ, ROOT_CLOCK_RATE, branch); + else if (clk_mgt_val == PRCM_CLK_MGT_CLKPLLSW_SOC1) + return pll_rate(PRCM_PLLSOC1_FREQ, ROOT_CLOCK_RATE, branch); + else if (clk_mgt_val == PRCM_CLK_MGT_CLKPLLSW_DDR) + return pll_rate(PRCM_PLLDDR_FREQ, ROOT_CLOCK_RATE, branch); + else + return 0; +} + +static u32 clock_divider(unsigned long src_rate, unsigned long rate) +{ + u32 div; + + div = (src_rate / rate); + if (div == 0) + return 1; + if (rate < (src_rate / div)) + div++; + if (div > 31) + div = 31; + return div; +} + +static long round_clock_rate(u8 clock, unsigned long rate) +{ + u32 val; + u32 div; + unsigned long src_rate; + long rounded_rate; + + val = readl(clk_mgt[clock].reg); + src_rate = clock_source_rate((val | clk_mgt[clock].pllsw), + clk_mgt[clock].branch); + div = clock_divider(src_rate, rate); + if (val & PRCM_CLK_MGT_CLK38) { + if (clk_mgt[clock].clk38div) { + if (div > 2) + div = 2; + } else { + div = 1; + } + } else if ((clock == PRCMU_SGACLK) && (div == 3)) { + u64 r = (src_rate * 10); + + (void)do_div(r, 25); + if (r <= rate) + return (unsigned long)r; + } + rounded_rate = (src_rate / div); + + return rounded_rate; +} + +long prcmu_round_clock_rate(u8 clock, unsigned long rate) +{ + if (clock < PRCMU_NUM_REG_CLOCKS) + return round_clock_rate(clock, rate); + else + return (long)prcmu_clock_rate(clock); +} + +static void set_clock_rate(u8 clock, unsigned long rate) +{ + u32 val; + u32 div; + unsigned long src_rate; + unsigned long flags; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + val = readl(clk_mgt[clock].reg); + src_rate = clock_source_rate((val | clk_mgt[clock].pllsw), + clk_mgt[clock].branch); + div = clock_divider(src_rate, rate); + if (val & PRCM_CLK_MGT_CLK38) { + if (clk_mgt[clock].clk38div) { + if (div > 1) + val |= PRCM_CLK_MGT_CLK38DIV; + else + val &= ~PRCM_CLK_MGT_CLK38DIV; + } + } else if (clock == PRCMU_SGACLK) { + val &= ~(PRCM_CLK_MGT_CLKPLLDIV_MASK | + PRCM_SGACLK_MGT_SGACLKDIV_BY_2_5_EN); + if (div == 3) { + u64 r = (src_rate * 10); + + (void)do_div(r, 25); + if (r <= rate) { + val |= PRCM_SGACLK_MGT_SGACLKDIV_BY_2_5_EN; + div = 0; + } + } + val |= div; + } else { + val &= ~PRCM_CLK_MGT_CLKPLLDIV_MASK; + val |= div; + } + writel(val, clk_mgt[clock].reg); + + /* Release the HW semaphore. */ + writel(0, PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); +} + +int prcmu_set_clock_rate(u8 clock, unsigned long rate) +{ + if (clock < PRCMU_NUM_REG_CLOCKS) + set_clock_rate(clock, rate); + return 0; +} + +int db8500_prcmu_config_esram0_deep_sleep(u8 state) { if ((state > ESRAM0_DEEP_SLEEP_STATE_RET) || (state < ESRAM0_DEEP_SLEEP_STATE_OFF)) @@ -1238,7 +1752,7 @@ int prcmu_config_esram0_deep_sleep(u8 state) mutex_lock(&mb4_transfer.lock); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) cpu_relax(); writeb(MB4H_MEM_ST, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); @@ -1248,7 +1762,7 @@ int prcmu_config_esram0_deep_sleep(u8 state) (tcdm_base + PRCM_REQ_MB4_DDR_ST_AP_DEEP_IDLE)); writeb(state, (tcdm_base + PRCM_REQ_MB4_ESRAM0_ST)); - writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(4), PRCM_MBOX_CPU_SET); wait_for_completion(&mb4_transfer.work); mutex_unlock(&mb4_transfer.lock); @@ -1256,17 +1770,17 @@ int prcmu_config_esram0_deep_sleep(u8 state) return 0; } -int prcmu_config_hotdog(u8 threshold) +int db8500_prcmu_config_hotdog(u8 threshold) { mutex_lock(&mb4_transfer.lock); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) cpu_relax(); writeb(threshold, (tcdm_base + PRCM_REQ_MB4_HOTDOG_THRESHOLD)); writeb(MB4H_HOTDOG, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); - writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(4), PRCM_MBOX_CPU_SET); wait_for_completion(&mb4_transfer.work); mutex_unlock(&mb4_transfer.lock); @@ -1274,11 +1788,11 @@ int prcmu_config_hotdog(u8 threshold) return 0; } -int prcmu_config_hotmon(u8 low, u8 high) +int db8500_prcmu_config_hotmon(u8 low, u8 high) { mutex_lock(&mb4_transfer.lock); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) cpu_relax(); writeb(low, (tcdm_base + PRCM_REQ_MB4_HOTMON_LOW)); @@ -1287,7 +1801,7 @@ int prcmu_config_hotmon(u8 low, u8 high) (tcdm_base + PRCM_REQ_MB4_HOTMON_CONFIG)); writeb(MB4H_HOTMON, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); - writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(4), PRCM_MBOX_CPU_SET); wait_for_completion(&mb4_transfer.work); mutex_unlock(&mb4_transfer.lock); @@ -1299,13 +1813,13 @@ static int config_hot_period(u16 val) { mutex_lock(&mb4_transfer.lock); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) cpu_relax(); writew(val, (tcdm_base + PRCM_REQ_MB4_HOT_PERIOD)); writeb(MB4H_HOT_PERIOD, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); - writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(4), PRCM_MBOX_CPU_SET); wait_for_completion(&mb4_transfer.work); mutex_unlock(&mb4_transfer.lock); @@ -1313,7 +1827,7 @@ static int config_hot_period(u16 val) return 0; } -int prcmu_start_temp_sense(u16 cycles32k) +int db8500_prcmu_start_temp_sense(u16 cycles32k) { if (cycles32k == 0xFFFF) return -EINVAL; @@ -1321,44 +1835,73 @@ int prcmu_start_temp_sense(u16 cycles32k) return config_hot_period(cycles32k); } -int prcmu_stop_temp_sense(void) +int db8500_prcmu_stop_temp_sense(void) { return config_hot_period(0xFFFF); } -/** - * prcmu_set_clock_divider() - Configure the clock divider. - * @clock: The clock for which the request is made. - * @divider: The clock divider. (< 32) - * - * This function should only be used by the clock implementation. - * Do not use it from any other place! - */ -int prcmu_set_clock_divider(u8 clock, u8 divider) +static int prcmu_a9wdog(u8 cmd, u8 d0, u8 d1, u8 d2, u8 d3) { - u32 val; - unsigned long flags; - - if ((clock >= PRCMU_NUM_REG_CLOCKS) || (divider < 1) || (31 < divider)) - return -EINVAL; - spin_lock_irqsave(&clk_mgt_lock, flags); + mutex_lock(&mb4_transfer.lock); - /* Grab the HW semaphore. */ - while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) cpu_relax(); - val = readl(_PRCMU_BASE + clk_mgt[clock].offset); - val &= ~(PRCM_CLK_MGT_CLKPLLDIV_MASK); - val |= (u32)divider; - writel(val, (_PRCMU_BASE + clk_mgt[clock].offset)); + writeb(d0, (tcdm_base + PRCM_REQ_MB4_A9WDOG_0)); + writeb(d1, (tcdm_base + PRCM_REQ_MB4_A9WDOG_1)); + writeb(d2, (tcdm_base + PRCM_REQ_MB4_A9WDOG_2)); + writeb(d3, (tcdm_base + PRCM_REQ_MB4_A9WDOG_3)); - /* Release the HW semaphore. */ - writel(0, (_PRCMU_BASE + PRCM_SEM)); + writeb(cmd, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB4)); - spin_unlock_irqrestore(&clk_mgt_lock, flags); + writel(MBOX_BIT(4), PRCM_MBOX_CPU_SET); + wait_for_completion(&mb4_transfer.work); + + mutex_unlock(&mb4_transfer.lock); return 0; + +} + +int prcmu_config_a9wdog(u8 num, bool sleep_auto_off) +{ + BUG_ON(num == 0 || num > 0xf); + return prcmu_a9wdog(MB4H_A9WDOG_CONF, num, 0, 0, + sleep_auto_off ? A9WDOG_AUTO_OFF_EN : + A9WDOG_AUTO_OFF_DIS); +} + +int prcmu_enable_a9wdog(u8 id) +{ + return prcmu_a9wdog(MB4H_A9WDOG_EN, id, 0, 0, 0); +} + +int prcmu_disable_a9wdog(u8 id) +{ + return prcmu_a9wdog(MB4H_A9WDOG_DIS, id, 0, 0, 0); +} + +int prcmu_kick_a9wdog(u8 id) +{ + return prcmu_a9wdog(MB4H_A9WDOG_KICK, id, 0, 0, 0); +} + +/* + * timeout is 28 bit, in ms. + */ +int prcmu_load_a9wdog(u8 id, u32 timeout) +{ + return prcmu_a9wdog(MB4H_A9WDOG_LOAD, + (id & A9WDOG_ID_MASK) | + /* + * Put the lowest 28 bits of timeout at + * offset 4. Four first bits are used for id. + */ + (u8)((timeout << 4) & 0xf0), + (u8)((timeout >> 4) & 0xff), + (u8)((timeout >> 12) & 0xff), + (u8)((timeout >> 20) & 0xff)); } /** @@ -1380,7 +1923,7 @@ int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) mutex_lock(&mb5_transfer.lock); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) cpu_relax(); writeb(PRCMU_I2C_READ(slave), (tcdm_base + PRCM_REQ_MB5_I2C_SLAVE_OP)); @@ -1388,7 +1931,7 @@ int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) writeb(reg, (tcdm_base + PRCM_REQ_MB5_I2C_REG)); writeb(0, (tcdm_base + PRCM_REQ_MB5_I2C_VAL)); - writel(MBOX_BIT(5), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); if (!wait_for_completion_timeout(&mb5_transfer.work, msecs_to_jiffies(20000))) { @@ -1426,7 +1969,7 @@ int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) mutex_lock(&mb5_transfer.lock); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) cpu_relax(); writeb(PRCMU_I2C_WRITE(slave), (tcdm_base + PRCM_REQ_MB5_I2C_SLAVE_OP)); @@ -1434,7 +1977,7 @@ int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) writeb(reg, (tcdm_base + PRCM_REQ_MB5_I2C_REG)); writeb(*value, (tcdm_base + PRCM_REQ_MB5_I2C_VAL)); - writel(MBOX_BIT(5), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); if (!wait_for_completion_timeout(&mb5_transfer.work, msecs_to_jiffies(20000))) { @@ -1456,21 +1999,44 @@ int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) void prcmu_ac_wake_req(void) { u32 val; + u32 status; mutex_lock(&mb0_transfer.ac_wake_lock); - val = readl(_PRCMU_BASE + PRCM_HOSTACCESS_REQ); + val = readl(PRCM_HOSTACCESS_REQ); if (val & PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ) goto unlock_and_return; atomic_set(&ac_wake_req_state, 1); - writel((val | PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ), - (_PRCMU_BASE + PRCM_HOSTACCESS_REQ)); +retry: + writel((val | PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ), PRCM_HOSTACCESS_REQ); if (!wait_for_completion_timeout(&mb0_transfer.ac_wake_work, - msecs_to_jiffies(20000))) { - pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + msecs_to_jiffies(5000))) { + panic("prcmu: %s timed out (5 s) waiting for a reply.\n", + __func__); + goto unlock_and_return; + } + + /* + * The modem can generate an AC_WAKE_ACK, and then still go to sleep. + * As a workaround, we wait, and then check that the modem is indeed + * awake (in terms of the value of the PRCM_MOD_AWAKE_STATUS + * register, which may not be the whole truth). + */ + udelay(400); + status = (readl(PRCM_MOD_AWAKE_STATUS) & BITS(0, 2)); + if (status != (PRCM_MOD_AWAKE_STATUS_PRCM_MOD_AAPD_AWAKE | + PRCM_MOD_AWAKE_STATUS_PRCM_MOD_COREPD_AWAKE)) { + pr_err("prcmu: %s received ack, but modem not awake (0x%X).\n", + __func__, status); + udelay(1200); + writel(val, PRCM_HOSTACCESS_REQ); + if (wait_for_completion_timeout(&mb0_transfer.ac_wake_work, + msecs_to_jiffies(5000))) + goto retry; + panic("prcmu: %s timed out (5 s) waiting for AC_SLEEP_ACK.\n", __func__); } @@ -1487,16 +2053,16 @@ void prcmu_ac_sleep_req() mutex_lock(&mb0_transfer.ac_wake_lock); - val = readl(_PRCMU_BASE + PRCM_HOSTACCESS_REQ); + val = readl(PRCM_HOSTACCESS_REQ); if (!(val & PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ)) goto unlock_and_return; writel((val & ~PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ), - (_PRCMU_BASE + PRCM_HOSTACCESS_REQ)); + PRCM_HOSTACCESS_REQ); if (!wait_for_completion_timeout(&mb0_transfer.ac_wake_work, - msecs_to_jiffies(20000))) { - pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + msecs_to_jiffies(5000))) { + panic("prcmu: %s timed out (5 s) waiting for a reply.\n", __func__); } @@ -1506,21 +2072,32 @@ unlock_and_return: mutex_unlock(&mb0_transfer.ac_wake_lock); } -bool prcmu_is_ac_wake_requested(void) +bool db8500_prcmu_is_ac_wake_requested(void) { return (atomic_read(&ac_wake_req_state) != 0); } /** - * prcmu_system_reset - System reset + * db8500_prcmu_system_reset - System reset * - * Saves the reset reason code and then sets the APE_SOFRST register which + * Saves the reset reason code and then sets the APE_SOFTRST register which * fires interrupt to fw */ -void prcmu_system_reset(u16 reset_code) +void db8500_prcmu_system_reset(u16 reset_code) { writew(reset_code, (tcdm_base + PRCM_SW_RST_REASON)); - writel(1, (_PRCMU_BASE + PRCM_APE_SOFTRST)); + writel(1, PRCM_APE_SOFTRST); +} + +/** + * db8500_prcmu_get_reset_code - Retrieve SW reset reason code + * + * Retrieves the reset reason code stored by prcmu_system_reset() before + * last restart. + */ +u16 db8500_prcmu_get_reset_code(void) +{ + return readw(tcdm_base + PRCM_SW_RST_REASON); } /** @@ -1530,11 +2107,11 @@ void prcmu_modem_reset(void) { mutex_lock(&mb1_transfer.lock); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) cpu_relax(); writeb(MB1H_RESET_MODEM, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); - writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); wait_for_completion(&mb1_transfer.work); /* @@ -1551,11 +2128,11 @@ static void ack_dbb_wakeup(void) spin_lock_irqsave(&mb0_transfer.lock, flags); - while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) cpu_relax(); writeb(MB0H_READ_WAKEUP_ACK, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB0)); - writel(MBOX_BIT(0), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + writel(MBOX_BIT(0), PRCM_MBOX_CPU_SET); spin_unlock_irqrestore(&mb0_transfer.lock, flags); } @@ -1600,7 +2177,7 @@ static bool read_mailbox_0(void) r = false; break; } - writel(MBOX_BIT(0), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + writel(MBOX_BIT(0), PRCM_ARM_IT1_CLR); return r; } @@ -1613,7 +2190,7 @@ static bool read_mailbox_1(void) PRCM_ACK_MB1_CURRENT_APE_OPP); mb1_transfer.ack.ape_voltage_status = readb(tcdm_base + PRCM_ACK_MB1_APE_VOLTAGE_STATUS); - writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + writel(MBOX_BIT(1), PRCM_ARM_IT1_CLR); complete(&mb1_transfer.work); return false; } @@ -1621,14 +2198,14 @@ static bool read_mailbox_1(void) static bool read_mailbox_2(void) { mb2_transfer.ack.status = readb(tcdm_base + PRCM_ACK_MB2_DPS_STATUS); - writel(MBOX_BIT(2), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + writel(MBOX_BIT(2), PRCM_ARM_IT1_CLR); complete(&mb2_transfer.work); return false; } static bool read_mailbox_3(void) { - writel(MBOX_BIT(3), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + writel(MBOX_BIT(3), PRCM_ARM_IT1_CLR); return false; } @@ -1643,6 +2220,11 @@ static bool read_mailbox_4(void) case MB4H_HOTDOG: case MB4H_HOTMON: case MB4H_HOT_PERIOD: + case MB4H_A9WDOG_CONF: + case MB4H_A9WDOG_EN: + case MB4H_A9WDOG_DIS: + case MB4H_A9WDOG_LOAD: + case MB4H_A9WDOG_KICK: break; default: print_unknown_header_warning(4, header); @@ -1650,7 +2232,7 @@ static bool read_mailbox_4(void) break; } - writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + writel(MBOX_BIT(4), PRCM_ARM_IT1_CLR); if (do_complete) complete(&mb4_transfer.work); @@ -1662,20 +2244,20 @@ static bool read_mailbox_5(void) { mb5_transfer.ack.status = readb(tcdm_base + PRCM_ACK_MB5_I2C_STATUS); mb5_transfer.ack.value = readb(tcdm_base + PRCM_ACK_MB5_I2C_VAL); - writel(MBOX_BIT(5), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + writel(MBOX_BIT(5), PRCM_ARM_IT1_CLR); complete(&mb5_transfer.work); return false; } static bool read_mailbox_6(void) { - writel(MBOX_BIT(6), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + writel(MBOX_BIT(6), PRCM_ARM_IT1_CLR); return false; } static bool read_mailbox_7(void) { - writel(MBOX_BIT(7), (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + writel(MBOX_BIT(7), PRCM_ARM_IT1_CLR); return false; } @@ -1696,7 +2278,7 @@ static irqreturn_t prcmu_irq_handler(int irq, void *data) u8 n; irqreturn_t r; - bits = (readl(_PRCMU_BASE + PRCM_ARM_IT1_VAL) & ALL_MBOX_BITS); + bits = (readl(PRCM_ARM_IT1_VAL) & ALL_MBOX_BITS); if (unlikely(!bits)) return IRQ_NONE; @@ -1768,13 +2350,10 @@ static struct irq_chip prcmu_irq_chip = { .irq_unmask = prcmu_irq_unmask, }; -void __init prcmu_early_init(void) +void __init db8500_prcmu_early_init(void) { unsigned int i; - - if (cpu_is_u8500v1()) { - tcdm_base = __io_address(U8500_PRCMU_TCDM_BASE_V1); - } else if (cpu_is_u8500v2()) { + if (cpu_is_u8500v2()) { void *tcpm_base = ioremap_nocache(U8500_PRCMU_TCPM_BASE, SZ_4K); if (tcpm_base != NULL) { @@ -1802,6 +2381,7 @@ void __init prcmu_early_init(void) init_completion(&mb0_transfer.ac_wake_work); mutex_init(&mb1_transfer.lock); init_completion(&mb1_transfer.work); + mb1_transfer.ape_opp = APE_NO_CHANGE; mutex_init(&mb2_transfer.lock); init_completion(&mb2_transfer.work); spin_lock_init(&mb2_transfer.auto_pm_lock); @@ -1826,6 +2406,16 @@ void __init prcmu_early_init(void) } } +static void __init init_prcm_registers(void) +{ + u32 val; + + val = readl(PRCM_A9PL_FORCE_CLKEN); + val &= ~(PRCM_A9PL_FORCE_CLKEN_PRCM_A9PL_FORCE_CLKEN | + PRCM_A9PL_FORCE_CLKEN_PRCM_A9AXI_FORCE_CLKEN); + writel(val, (PRCM_A9PL_FORCE_CLKEN)); +} + /* * Power domain switches (ePODs) modeled as regulators for the DB8500 SoC */ @@ -1843,25 +2433,57 @@ static struct regulator_consumer_supply db8500_vape_consumers[] = { REGULATOR_SUPPLY("vcore", "sdi4"), REGULATOR_SUPPLY("v-dma", "dma40.0"), REGULATOR_SUPPLY("v-ape", "ab8500-usb.0"), - /* "v-uart" changed to "vcore" in the mainline kernel */ - REGULATOR_SUPPLY("vcore", "uart0"), - REGULATOR_SUPPLY("vcore", "uart1"), - REGULATOR_SUPPLY("vcore", "uart2"), + REGULATOR_SUPPLY("v-uart", "uart0"), + REGULATOR_SUPPLY("v-uart", "uart1"), + REGULATOR_SUPPLY("v-uart", "uart2"), REGULATOR_SUPPLY("v-ape", "nmk-ske-keypad.0"), }; static struct regulator_consumer_supply db8500_vsmps2_consumers[] = { - /* CG2900 and CW1200 power to off-chip peripherals */ - REGULATOR_SUPPLY("gbf_1v8", "cg2900-uart.0"), - REGULATOR_SUPPLY("wlan_1v8", "cw1200.0"), REGULATOR_SUPPLY("musb_1v8", "ab8500-usb.0"), /* AV8100 regulator */ REGULATOR_SUPPLY("hdmi_1v8", "0-0070"), }; static struct regulator_consumer_supply db8500_b2r2_mcde_consumers[] = { - REGULATOR_SUPPLY("vsupply", "b2r2.0"), - REGULATOR_SUPPLY("vsupply", "mcde.0"), + REGULATOR_SUPPLY("vsupply", "b2r2_bus"), + REGULATOR_SUPPLY("vsupply", "mcde"), +}; + +/* SVA MMDSP regulator switch */ +static struct regulator_consumer_supply db8500_svammdsp_consumers[] = { + REGULATOR_SUPPLY("sva-mmdsp", "cm_control"), +}; + +/* SVA pipe regulator switch */ +static struct regulator_consumer_supply db8500_svapipe_consumers[] = { + REGULATOR_SUPPLY("sva-pipe", "cm_control"), +}; + +/* SIA MMDSP regulator switch */ +static struct regulator_consumer_supply db8500_siammdsp_consumers[] = { + REGULATOR_SUPPLY("sia-mmdsp", "cm_control"), +}; + +/* SIA pipe regulator switch */ +static struct regulator_consumer_supply db8500_siapipe_consumers[] = { + REGULATOR_SUPPLY("sia-pipe", "cm_control"), +}; + +static struct regulator_consumer_supply db8500_sga_consumers[] = { + REGULATOR_SUPPLY("v-mali", NULL), +}; + +/* ESRAM1 and 2 regulator switch */ +static struct regulator_consumer_supply db8500_esram12_consumers[] = { + REGULATOR_SUPPLY("esram12", "cm_control"), +}; + +/* ESRAM3 and 4 regulator switch */ +static struct regulator_consumer_supply db8500_esram34_consumers[] = { + REGULATOR_SUPPLY("v-esram34", "mcde"), + REGULATOR_SUPPLY("esram34", "cm_control"), + REGULATOR_SUPPLY("lcla_esram", "dma40.0"), }; static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { @@ -1923,6 +2545,8 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { .name = "db8500-sva-mmdsp", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, + .consumer_supplies = db8500_svammdsp_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_svammdsp_consumers), }, [DB8500_REGULATOR_SWITCH_SVAMMDSPRET] = { .constraints = { @@ -1937,6 +2561,8 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { .name = "db8500-sva-pipe", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, + .consumer_supplies = db8500_svapipe_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_svapipe_consumers), }, [DB8500_REGULATOR_SWITCH_SIAMMDSP] = { .supply_regulator = "db8500-vape", @@ -1944,6 +2570,8 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { .name = "db8500-sia-mmdsp", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, + .consumer_supplies = db8500_siammdsp_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_siammdsp_consumers), }, [DB8500_REGULATOR_SWITCH_SIAMMDSPRET] = { .constraints = { @@ -1957,6 +2585,8 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { .name = "db8500-sia-pipe", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, + .consumer_supplies = db8500_siapipe_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_siapipe_consumers), }, [DB8500_REGULATOR_SWITCH_SGA] = { .supply_regulator = "db8500-vape", @@ -1964,6 +2594,9 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { .name = "db8500-sga", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, + .consumer_supplies = db8500_sga_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_sga_consumers), + }, [DB8500_REGULATOR_SWITCH_B2R2_MCDE] = { .supply_regulator = "db8500-vape", @@ -1980,6 +2613,8 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { .name = "db8500-esram12", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, + .consumer_supplies = db8500_esram12_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_esram12_consumers), }, [DB8500_REGULATOR_SWITCH_ESRAM12RET] = { .constraints = { @@ -1993,6 +2628,8 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = { .name = "db8500-esram34", .valid_ops_mask = REGULATOR_CHANGE_STATUS, }, + .consumer_supplies = db8500_esram34_consumers, + .num_consumer_supplies = ARRAY_SIZE(db8500_esram34_consumers), }, [DB8500_REGULATOR_SWITCH_ESRAM34RET] = { .constraints = { @@ -2024,8 +2661,10 @@ static int __init db8500_prcmu_probe(struct platform_device *pdev) if (ux500_is_svp()) return -ENODEV; + init_prcm_registers(); + /* Clean up the mailbox interrupts after pre-kernel code. */ - writel(ALL_MBOX_BITS, (_PRCMU_BASE + PRCM_ARM_IT1_CLR)); + writel(ALL_MBOX_BITS, PRCM_ARM_IT1_CLR); err = request_threaded_irq(IRQ_DB8500_PRCMU1, prcmu_irq_handler, prcmu_irq_thread_fn, IRQF_NO_SUSPEND, "prcmu", NULL); diff --git a/drivers/mfd/dbx500-prcmu-regs.h b/drivers/mfd/dbx500-prcmu-regs.h new file mode 100644 index 00000000000..2da628d11db --- /dev/null +++ b/drivers/mfd/dbx500-prcmu-regs.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Kumar Sanghvi <kumar.sanghvi@stericsson.com> + * Author: Sundar Iyer <sundar.iyer@stericsson.com> + * + * License Terms: GNU General Public License v2 + * + * PRCM Unit registers + */ + +#ifndef __DB8500_PRCMU_REGS_H +#define __DB8500_PRCMU_REGS_H + +#include <mach/hardware.h> + +#define BITS(_start, _end) ((BIT(_end) - BIT(_start)) + BIT(_end)) + +#define PRCM_CLK_MGT(_offset) (void __iomem *)(IO_ADDRESS(U8500_PRCMU_BASE) \ + + _offset) +#define PRCM_ACLK_MGT PRCM_CLK_MGT(0x004) +#define PRCM_SVACLK_MGT PRCM_CLK_MGT(0x008) +#define PRCM_SIACLK_MGT PRCM_CLK_MGT(0x00C) +#define PRCM_SGACLK_MGT PRCM_CLK_MGT(0x014) +#define PRCM_UARTCLK_MGT PRCM_CLK_MGT(0x018) +#define PRCM_MSP02CLK_MGT PRCM_CLK_MGT(0x01C) +#define PRCM_I2CCLK_MGT PRCM_CLK_MGT(0x020) +#define PRCM_SDMMCCLK_MGT PRCM_CLK_MGT(0x024) +#define PRCM_SLIMCLK_MGT PRCM_CLK_MGT(0x028) +#define PRCM_PER1CLK_MGT PRCM_CLK_MGT(0x02C) +#define PRCM_PER2CLK_MGT PRCM_CLK_MGT(0x030) +#define PRCM_PER3CLK_MGT PRCM_CLK_MGT(0x034) +#define PRCM_PER5CLK_MGT PRCM_CLK_MGT(0x038) +#define PRCM_PER6CLK_MGT PRCM_CLK_MGT(0x03C) +#define PRCM_PER7CLK_MGT PRCM_CLK_MGT(0x040) +#define PRCM_LCDCLK_MGT PRCM_CLK_MGT(0x044) +#define PRCM_BMLCLK_MGT PRCM_CLK_MGT(0x04C) +#define PRCM_HSITXCLK_MGT PRCM_CLK_MGT(0x050) +#define PRCM_HSIRXCLK_MGT PRCM_CLK_MGT(0x054) +#define PRCM_HDMICLK_MGT PRCM_CLK_MGT(0x058) +#define PRCM_APEATCLK_MGT PRCM_CLK_MGT(0x05C) +#define PRCM_APETRACECLK_MGT PRCM_CLK_MGT(0x060) +#define PRCM_MCDECLK_MGT PRCM_CLK_MGT(0x064) +#define PRCM_IPI2CCLK_MGT PRCM_CLK_MGT(0x068) +#define PRCM_DSIALTCLK_MGT PRCM_CLK_MGT(0x06C) +#define PRCM_DMACLK_MGT PRCM_CLK_MGT(0x074) +#define PRCM_B2R2CLK_MGT PRCM_CLK_MGT(0x078) +#define PRCM_TVCLK_MGT PRCM_CLK_MGT(0x07C) +#define PRCM_UNIPROCLK_MGT PRCM_CLK_MGT(0x278) +#define PRCM_SSPCLK_MGT PRCM_CLK_MGT(0x280) +#define PRCM_RNGCLK_MGT PRCM_CLK_MGT(0x284) +#define PRCM_UICCCLK_MGT PRCM_CLK_MGT(0x27C) +#define PRCM_MSP1CLK_MGT PRCM_CLK_MGT(0x288) + +#define PRCM_ARM_PLLDIVPS (_PRCMU_BASE + 0x118) +#define PRCM_ARM_PLLDIVPS_ARM_BRM_RATE 0x3f +#define PRCM_ARM_PLLDIVPS_MAX_MASK 0xf + +#define PRCM_PLLARM_LOCKP (_PRCMU_BASE + 0x0a8) +#define PRCM_PLLARM_LOCKP_PRCM_PLLARM_LOCKP3 0x2 + +#define PRCM_ARM_CHGCLKREQ (_PRCMU_BASE + 0x114) +#define PRCM_ARM_CHGCLKREQ_PRCM_ARM_CHGCLKREQ 0x1 + +#define PRCM_PLLARM_ENABLE (_PRCMU_BASE + 0x98) +#define PRCM_PLLARM_ENABLE_PRCM_PLLARM_ENABLE 0x1 +#define PRCM_PLLARM_ENABLE_PRCM_PLLARM_COUNTON 0x100 + +#define PRCM_ARMCLKFIX_MGT (_PRCMU_BASE + 0x0) +#define PRCM_A9PL_FORCE_CLKEN (_PRCMU_BASE + 0x19C) +#define PRCM_A9_RESETN_CLR (_PRCMU_BASE + 0x1f4) +#define PRCM_A9_RESETN_SET (_PRCMU_BASE + 0x1f0) +#define PRCM_ARM_LS_CLAMP (_PRCMU_BASE + 0x30c) +#define PRCM_SRAM_A9 (_PRCMU_BASE + 0x308) + +#define PRCM_A9PL_FORCE_CLKEN_PRCM_A9PL_FORCE_CLKEN BIT(0) +#define PRCM_A9PL_FORCE_CLKEN_PRCM_A9AXI_FORCE_CLKEN BIT(1) + +/* ARM WFI Standby signal register */ +#define PRCM_ARM_WFI_STANDBY (_PRCMU_BASE + 0x130) +#define PRCM_IOCR (_PRCMU_BASE + 0x310) +#define PRCM_IOCR_IOFORCE 0x1 + +/* CPU mailbox registers */ +#define PRCM_MBOX_CPU_VAL (_PRCMU_BASE + 0x0fc) +#define PRCM_MBOX_CPU_SET (_PRCMU_BASE + 0x100) +#define PRCM_MBOX_CPU_CLR (_PRCMU_BASE + 0x104) + +/* Dual A9 core interrupt management unit registers */ +#define PRCM_A9_MASK_REQ (_PRCMU_BASE + 0x328) +#define PRCM_A9_MASK_REQ_PRCM_A9_MASK_REQ 0x1 + +#define PRCM_A9_MASK_ACK (_PRCMU_BASE + 0x32c) +#define PRCM_ARMITMSK31TO0 (_PRCMU_BASE + 0x11c) +#define PRCM_ARMITMSK63TO32 (_PRCMU_BASE + 0x120) +#define PRCM_ARMITMSK95TO64 (_PRCMU_BASE + 0x124) +#define PRCM_ARMITMSK127TO96 (_PRCMU_BASE + 0x128) +#define PRCM_POWER_STATE_VAL (_PRCMU_BASE + 0x25C) +#define PRCM_ARMITVAL31TO0 (_PRCMU_BASE + 0x260) +#define PRCM_ARMITVAL63TO32 (_PRCMU_BASE + 0x264) +#define PRCM_ARMITVAL95TO64 (_PRCMU_BASE + 0x268) +#define PRCM_ARMITVAL127TO96 (_PRCMU_BASE + 0x26C) + +#define PRCM_HOSTACCESS_REQ (_PRCMU_BASE + 0x334) +#define PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ 0x1 +#define ARM_WAKEUP_MODEM 0x1 + +#define PRCM_ARM_IT1_CLR (_PRCMU_BASE + 0x48C) +#define PRCM_ARM_IT1_VAL (_PRCMU_BASE + 0x494) +#define PRCM_HOLD_EVT (_PRCMU_BASE + 0x174) + +#define PRCM_MOD_AWAKE_STATUS (_PRCMU_BASE + 0x4A0) +#define PRCM_MOD_AWAKE_STATUS_PRCM_MOD_COREPD_AWAKE BIT(0) +#define PRCM_MOD_AWAKE_STATUS_PRCM_MOD_AAPD_AWAKE BIT(1) +#define PRCM_MOD_AWAKE_STATUS_PRCM_MOD_VMODEM_OFF_ISO BIT(2) + +#define PRCM_ITSTATUS0 (_PRCMU_BASE + 0x148) +#define PRCM_ITSTATUS1 (_PRCMU_BASE + 0x150) +#define PRCM_ITSTATUS2 (_PRCMU_BASE + 0x158) +#define PRCM_ITSTATUS3 (_PRCMU_BASE + 0x160) +#define PRCM_ITSTATUS4 (_PRCMU_BASE + 0x168) +#define PRCM_ITSTATUS5 (_PRCMU_BASE + 0x484) +#define PRCM_ITCLEAR5 (_PRCMU_BASE + 0x488) +#define PRCM_ARMIT_MASKXP70_IT (_PRCMU_BASE + 0x1018) + +/* System reset register */ +#define PRCM_APE_SOFTRST (_PRCMU_BASE + 0x228) + +/* Level shifter and clamp control registers */ +#define PRCM_MMIP_LS_CLAMP_SET (_PRCMU_BASE + 0x420) +#define PRCM_MMIP_LS_CLAMP_CLR (_PRCMU_BASE + 0x424) + +/* PRCMU clock/PLL/reset registers */ +#define PRCM_PLLSOC0_FREQ (_PRCMU_BASE + 0x080) +#define PRCM_PLLSOC1_FREQ (_PRCMU_BASE + 0x084) +#define PRCM_PLLDDR_FREQ (_PRCMU_BASE + 0x08C) +#define PRCM_PLL_FREQ_D_SHIFT 0 +#define PRCM_PLL_FREQ_D_MASK BITS(0, 7) +#define PRCM_PLL_FREQ_N_SHIFT 8 +#define PRCM_PLL_FREQ_N_MASK BITS(8, 13) +#define PRCM_PLL_FREQ_R_SHIFT 16 +#define PRCM_PLL_FREQ_R_MASK BITS(16, 18) +#define PRCM_PLL_FREQ_SELDIV2 BIT(24) +#define PRCM_PLL_FREQ_DIV2EN BIT(25) + +#define PRCM_PLLDSI_FREQ (_PRCMU_BASE + 0x500) +#define PRCM_PLLDSI_ENABLE (_PRCMU_BASE + 0x504) +#define PRCM_PLLDSI_LOCKP (_PRCMU_BASE + 0x508) +#define PRCM_DSI_PLLOUT_SEL (_PRCMU_BASE + 0x530) +#define PRCM_DSITVCLK_DIV (_PRCMU_BASE + 0x52C) +#define PRCM_PLLDSI_LOCKP (_PRCMU_BASE + 0x508) +#define PRCM_APE_RESETN_SET (_PRCMU_BASE + 0x1E4) +#define PRCM_APE_RESETN_CLR (_PRCMU_BASE + 0x1E8) + +#define PRCM_CLKOCR (_PRCMU_BASE + 0x1CC) +#define PRCM_CLKOCR_CLKOUT0_REF_CLK (1 << 0) +#define PRCM_CLKOCR_CLKOUT0_MASK BITS(0, 13) +#define PRCM_CLKOCR_CLKOUT1_REF_CLK (1 << 16) +#define PRCM_CLKOCR_CLKOUT1_MASK BITS(16, 29) + +/* ePOD and memory power signal control registers */ +#define PRCM_EPOD_C_SET (_PRCMU_BASE + 0x410) +#define PRCM_SRAM_LS_SLEEP (_PRCMU_BASE + 0x304) + +/* Debug power control unit registers */ +#define PRCM_POWER_STATE_SET (_PRCMU_BASE + 0x254) + +/* Miscellaneous unit registers */ +#define PRCM_DSI_SW_RESET (_PRCMU_BASE + 0x324) +#define PRCM_GPIOCR (_PRCMU_BASE + 0x138) +#define PRCM_GPIOCR_DBG_STM_MOD_CMD1 0x800 +#define PRCM_GPIOCR_DBG_UARTMOD_CMD0 0x1 + +/* PRCMU HW semaphore */ +#define PRCM_SEM (_PRCMU_BASE + 0x400) +#define PRCM_SEM_PRCM_SEM BIT(0) + +#define PRCM_TCR (_PRCMU_BASE + 0x1C8) +#define PRCM_TCR_TENSEL_MASK BITS(0, 7) +#define PRCM_TCR_STOP_TIMERS BIT(16) +#define PRCM_TCR_DOZE_MODE BIT(17) + +#define PRCM_CLKOCR_CLKODIV0_SHIFT 0 +#define PRCM_CLKOCR_CLKODIV0_MASK BITS(0, 5) +#define PRCM_CLKOCR_CLKOSEL0_SHIFT 6 +#define PRCM_CLKOCR_CLKOSEL0_MASK BITS(6, 8) +#define PRCM_CLKOCR_CLKODIV1_SHIFT 16 +#define PRCM_CLKOCR_CLKODIV1_MASK BITS(16, 21) +#define PRCM_CLKOCR_CLKOSEL1_SHIFT 22 +#define PRCM_CLKOCR_CLKOSEL1_MASK BITS(22, 24) +#define PRCM_CLKOCR_CLK1TYPE BIT(28) + +#define PRCM_CLK_MGT_CLKPLLDIV_MASK BITS(0, 4) +#define PRCM_CLK_MGT_CLKPLLSW_SOC0 BIT(5) +#define PRCM_CLK_MGT_CLKPLLSW_SOC1 BIT(6) +#define PRCM_CLK_MGT_CLKPLLSW_DDR BIT(7) +#define PRCM_CLK_MGT_CLKPLLSW_MASK BITS(5, 7) +#define PRCM_CLK_MGT_CLKEN BIT(8) +#define PRCM_CLK_MGT_CLK38 BIT(9) +#define PRCM_CLK_MGT_CLK38DIV BIT(11) +#define PRCM_SGACLK_MGT_SGACLKDIV_BY_2_5_EN BIT(12) + +/* GPIOCR register */ +#define PRCM_GPIOCR_SPI2_SELECT BIT(23) + +#define PRCM_DDR_SUBSYS_APE_MINBW (_PRCMU_BASE + 0x438) +#define PRCM_CGATING_BYPASS (_PRCMU_BASE + 0x134) +#define PRCM_CGATING_BYPASS_ICN2 BIT(6) + +/* Miscellaneous unit registers */ +#define PRCM_RESOUTN_SET (_PRCMU_BASE + 0x214) +#define PRCM_RESOUTN_CLR (_PRCMU_BASE + 0x218) + +/* System reset register */ +#define PRCM_APE_SOFTRST (_PRCMU_BASE + 0x228) + +#endif /* __DB8500_PRCMU_REGS_H */ diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index 2963689cf45..f9abf20975b 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -674,7 +674,7 @@ static irqreturn_t stmpe_irq(int irq, void *data) ret = stmpe_block_read(stmpe, israddr, num, isr); if (ret < 0) return IRQ_NONE; - +back: for (i = 0; i < num; i++) { int bank = num - i - 1; u8 status = isr[i]; @@ -696,6 +696,22 @@ static irqreturn_t stmpe_irq(int irq, void *data) stmpe_reg_write(stmpe, israddr + i, clear); } + /* + It may happen that on the first status read interrupt + sources may not showup, so read one more time. + */ + ret = stmpe_block_read(stmpe, israddr, num, isr); + if (ret >= 0) { + for (i = 0; i < num; i++) { + int bank = num - i - 1; + u8 status = isr[i]; + + status &= stmpe->ier[bank]; + if (status) + goto back; + } + } + return IRQ_HANDLED; } diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c new file mode 100644 index 00000000000..91211f29623 --- /dev/null +++ b/drivers/mfd/tc35892.c @@ -0,0 +1,503 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Hanumath Prasad <hanumath.prasad@stericsson.com> for ST-Ericsson + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> +#include <linux/mfd/tc35892.h> + +#define TC35892_CLKMODE_MODCTL_SLEEP 0x0 +#define TC35892_CLKMODE_MODCTL_OPERATION (1 << 0) + +/** + * tc35892_reg_read() - read a single TC35892 register + * @tc35892: Device to read from + * @reg: Register to read + */ +int tc35892_reg_read(struct tc35892 *tc35892, u8 reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(tc35892->i2c, reg); + if (ret < 0) + dev_err(tc35892->dev, "failed to read reg %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_reg_read); + +/** + * tc35892_reg_read() - write a single TC35892 register + * @tc35892: Device to write to + * @reg: Register to read + * @data: Value to write + */ +int tc35892_reg_write(struct tc35892 *tc35892, u8 reg, u8 data) +{ + int ret; + + ret = i2c_smbus_write_byte_data(tc35892->i2c, reg, data); + if (ret < 0) + dev_err(tc35892->dev, "failed to write reg %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_reg_write); + +/** + * tc35892_block_read() - read multiple TC35892 registers + * @tc35892: Device to read from + * @reg: First register + * @length: Number of registers + * @values: Buffer to write to + */ +int tc35892_block_read(struct tc35892 *tc35892, u8 reg, u8 length, u8 *values) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(tc35892->i2c, reg, length, values); + if (ret < 0) + dev_err(tc35892->dev, "failed to read regs %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_block_read); + +/** + * tc35892_block_write() - write multiple TC35892 registers + * @tc35892: Device to write to + * @reg: First register + * @length: Number of registers + * @values: Values to write + */ +int tc35892_block_write(struct tc35892 *tc35892, u8 reg, u8 length, + const u8 *values) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(tc35892->i2c, reg, length, + values); + if (ret < 0) + dev_err(tc35892->dev, "failed to write regs %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_block_write); + +/** + * tc35892_set_bits() - set the value of a bitfield in a TC35892 register + * @tc35892: Device to write to + * @reg: Register to write + * @mask: Mask of bits to set + * @values: Value to set + */ +int tc35892_set_bits(struct tc35892 *tc35892, u8 reg, u8 mask, u8 val) +{ + int ret; + + mutex_lock(&tc35892->lock); + + ret = tc35892_reg_read(tc35892, reg); + if (ret < 0) + goto out; + + ret &= ~mask; + ret |= val; + + ret = tc35892_reg_write(tc35892, reg, ret); + +out: + mutex_unlock(&tc35892->lock); + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_set_bits); + +static struct resource gpio_resources[] = { + { + .start = TC35892_INT_GPIIRQ, + .end = TC35892_INT_GPIIRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell tc35892_devs[] = { + { + .name = "tc35892-gpio", + .num_resources = ARRAY_SIZE(gpio_resources), + .resources = &gpio_resources[0], + }, +}; + +static irqreturn_t tc35892_irq(int irq, void *data) +{ + struct tc35892 *tc35892 = data; + int status; + +again: + status = tc35892_reg_read(tc35892, TC35892_IRQST); + if (status < 0) + return IRQ_NONE; + + while (status) { + int bit = __ffs(status); + + handle_nested_irq(tc35892->irq_base + bit); + status &= ~(1 << bit); + } + + /* + * A dummy read or write (to any register) appears to be necessary to + * have the last interrupt clear (for example, GPIO IC write) take + * effect. In such a case, recheck for any interrupt which is still + * pending. + */ + status = tc35892_reg_read(tc35892, TC35892_IRQST); + if (status) + goto again; + + return IRQ_HANDLED; +} + +static void tc35892_irq_dummy(unsigned int irq) +{ + /* No mask/unmask at this level */ +} + +static struct irq_chip tc35892_irq_chip = { + .name = "tc35892", + .irq_mask = tc35892_irq_dummy, + .irq_unmask = tc35892_irq_dummy, +}; + +static int tc35892_irq_init(struct tc35892 *tc35892) +{ + int base = tc35892->irq_base; + int irq; + + for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) { + irq_set_chip_data(irq, tc35892); + irq_set_chip_and_handler(irq, &tc35892_irq_chip, + handle_edge_irq); + irq_set_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + } + + return 0; +} + +static void tc35892_irq_remove(struct tc35892 *tc35892) +{ + int base = tc35892->irq_base; + int irq; + + for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); + } +} + +static int tc35892_chip_init(struct tc35892 *tc35892) +{ + int manf, ver, ret; + + manf = tc35892_reg_read(tc35892, TC35892_MANFCODE); + if (manf < 0) + return manf; + + ver = tc35892_reg_read(tc35892, TC35892_VERSION); + if (ver < 0) + return ver; + + if (manf != TC35892_MANFCODE_MAGIC) { + dev_err(tc35892->dev, "unknown manufacturer: %#x\n", manf); + return -EINVAL; + } + + dev_info(tc35892->dev, "manufacturer: %#x, version: %#x\n", manf, ver); + + /* + * Put everything except the IRQ module into reset; + * also spare the GPIO module for any pin initialization + * done during pre-kernel boot + */ + ret = tc35892_reg_write(tc35892, TC35892_RSTCTRL, + TC35892_RSTCTRL_TIMRST + | TC35892_RSTCTRL_ROTRST + | TC35892_RSTCTRL_KBDRST); + if (ret < 0) + return ret; + + /* Clear the reset interrupt. */ + return tc35892_reg_write(tc35892, TC35892_RSTINTCLR, 0x1); +} + +static int __devinit tc35892_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tc35892_platform_data *pdata = i2c->dev.platform_data; + struct tc35892 *tc35892; + int ret; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EIO; + + tc35892 = kzalloc(sizeof(struct tc35892), GFP_KERNEL); + if (!tc35892) + return -ENOMEM; + + mutex_init(&tc35892->lock); + + tc35892->dev = &i2c->dev; + tc35892->i2c = i2c; + tc35892->pdata = pdata; + tc35892->irq_base = pdata->irq_base; + tc35892->num_gpio = id->driver_data; + + i2c_set_clientdata(i2c, tc35892); + + ret = tc35892_chip_init(tc35892); + if (ret) + goto out_free; + + ret = tc35892_irq_init(tc35892); + if (ret) + goto out_free; + + ret = request_threaded_irq(tc35892->i2c->irq, NULL, tc35892_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "tc35892", tc35892); + if (ret) { + dev_err(tc35892->dev, "failed to request IRQ: %d\n", ret); + goto out_removeirq; + } + + ret = mfd_add_devices(tc35892->dev, -1, tc35892_devs, + ARRAY_SIZE(tc35892_devs), NULL, + tc35892->irq_base); + if (ret) { + dev_err(tc35892->dev, "failed to add children\n"); + goto out_freeirq; + } + + return 0; + +out_freeirq: + free_irq(tc35892->i2c->irq, tc35892); +out_removeirq: + tc35892_irq_remove(tc35892); +out_free: + kfree(tc35892); + return ret; +} + +static int __devexit tc35892_remove(struct i2c_client *client) +{ + struct tc35892 *tc35892 = i2c_get_clientdata(client); + + mfd_remove_devices(tc35892->dev); + + free_irq(tc35892->i2c->irq, tc35892); + tc35892_irq_remove(tc35892); + + kfree(tc35892); + + return 0; +} + +#ifdef CONFIG_PM + +static u32 sleep_regs[] = { + TC35892_IOPC0_L, + TC35892_IOPC0_H, + TC35892_IOPC1_L, + TC35892_IOPC1_H, + TC35892_IOPC2_L, + TC35892_IOPC2_H, + TC35892_DRIVE0_L, + TC35892_DRIVE0_H, + TC35892_DRIVE1_L, + TC35892_DRIVE1_H, + TC35892_DRIVE2_L, + TC35892_DRIVE2_H, + TC35892_DRIVE3, + TC35892_GPIODATA0, + TC35892_GPIOMASK0, + TC35892_GPIODATA1, + TC35892_GPIOMASK1, + TC35892_GPIODATA2, + TC35892_GPIOMASK2, + TC35892_GPIODIR0, + TC35892_GPIODIR1, + TC35892_GPIODIR2, + TC35892_GPIOIE0, + TC35892_GPIOIE1, + TC35892_GPIOIE2, + TC35892_RSTCTRL, + TC35892_CLKCFG, +}; + +static u8 sleep_regs_val[] = { + 0x00, /* TC35892_IOPC0_L */ + 0x00, /* TC35892_IOPC0_H */ + 0x00, /* TC35892_IOPC1_L */ + 0x00, /* TC35892_IOPC1_H */ + 0x00, /* TC35892_IOPC2_L */ + 0x00, /* TC35892_IOPC2_H */ + 0xff, /* TC35892_DRIVE0_L */ + 0xff, /* TC35892_DRIVE0_H */ + 0xff, /* TC35892_DRIVE1_L */ + 0xff, /* TC35892_DRIVE1_H */ + 0xff, /* TC35892_DRIVE2_L */ + 0xff, /* TC35892_DRIVE2_H */ + 0x0f, /* TC35892_DRIVE3 */ + 0x80, /* TC35892_GPIODATA0 */ + 0x80, /* TC35892_GPIOMASK0 */ + 0x80, /* TC35892_GPIODATA1 */ + 0x80, /* TC35892_GPIOMASK1 */ + 0x06, /* TC35892_GPIODATA2 */ + 0x06, /* TC35892_GPIOMASK2 */ + 0xf0, /* TC35892_GPIODIR0 */ + 0xe0, /* TC35892_GPIODIR1 */ + 0xee, /* TC35892_GPIODIR2 */ + 0x0f, /* TC35892_GPIOIE0 */ + 0x1f, /* TC35892_GPIOIE1 */ + 0x11, /* TC35892_GPIOIE2 */ + 0x0f, /* TC35892_RSTCTRL */ + 0xb0 /* TC35892_CLKCFG */ + +}; + +static u8 sleep_regs_backup[ARRAY_SIZE(sleep_regs)]; + +static int tc35892_suspend(struct device *dev) +{ + struct tc35892 *tc35892 = dev_get_drvdata(dev); + struct i2c_client *client = tc35892->i2c; + int ret = 0; + int i, j; + int val; + + /* Put the system to sleep mode */ + if (!device_may_wakeup(&client->dev)) { + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + val = tc35892_reg_read(tc35892, + sleep_regs[i]); + if (val < 0) + goto out; + + sleep_regs_backup[i] = (u8) (val & 0xff); + } + + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + ret = tc35892_reg_write(tc35892, + sleep_regs[i], + sleep_regs_val[i]); + if (ret < 0) + goto fail; + + } + + ret = tc35892_reg_write(tc35892, + TC35892_CLKMODE, + TC35892_CLKMODE_MODCTL_SLEEP); + } +out: + return ret; +fail: + for (j = 0; j <= i; j++) { + ret = tc35892_reg_write(tc35892, + sleep_regs[i], + sleep_regs_backup[i]); + if (ret < 0) + break; + } + return ret; +} + +static int tc35892_resume(struct device *dev) +{ + struct tc35892 *tc35892 = dev_get_drvdata(dev); + struct i2c_client *client = tc35892->i2c; + int ret = 0; + int i; + + /* Enable the system into operation */ + if (!device_may_wakeup(&client->dev)) + { + ret = tc35892_reg_write(tc35892, + TC35892_CLKMODE, + TC35892_CLKMODE_MODCTL_OPERATION); + if (ret < 0) + goto out; + + for (i = ARRAY_SIZE(sleep_regs) - 1; i >= 0; i--) { + ret = tc35892_reg_write(tc35892, + sleep_regs[i], + sleep_regs_backup[i]); + /* Not much to do here if we fail */ + if (ret < 0) + break; + } + } +out: + return ret; +} + +static const struct dev_pm_ops tc35892_dev_pm_ops = { + .suspend = tc35892_suspend, + .resume = tc35892_resume, +}; +#endif + +static const struct i2c_device_id tc35892_id[] = { + { "tc35892", 24 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tc35892_id); + +static struct i2c_driver tc35892_driver = { + .driver.name = "tc35892", + .driver.owner = THIS_MODULE, +#ifdef CONFIG_PM + .driver.pm = &tc35892_dev_pm_ops, +#endif + .probe = tc35892_probe, + .remove = __devexit_p(tc35892_remove), + .id_table = tc35892_id, +}; + +static int __init tc35892_init(void) +{ + return i2c_add_driver(&tc35892_driver); +} +subsys_initcall(tc35892_init); + +static void __exit tc35892_exit(void) +{ + i2c_del_driver(&tc35892_driver); +} +module_exit(tc35892_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TC35892 MFD core driver"); +MODULE_AUTHOR("Hanumath Prasad, Rabin Vincent"); diff --git a/drivers/mfd/tc3589x.c b/drivers/mfd/tc3589x.c index c27e515b072..7bbcc6e332c 100644 --- a/drivers/mfd/tc3589x.c +++ b/drivers/mfd/tc3589x.c @@ -357,16 +357,113 @@ static int __devexit tc3589x_remove(struct i2c_client *client) return 0; } +static u32 sleep_regs[] = { + TC3589x_IOPC0_L, + TC3589x_IOPC0_H, + TC3589x_IOPC1_L, + TC3589x_IOPC1_H, + TC3589x_IOPC2_L, + TC3589x_IOPC2_H, + TC3589x_DRIVE0_L, + TC3589x_DRIVE0_H, + TC3589x_DRIVE1_L, + TC3589x_DRIVE1_H, + TC3589x_DRIVE2_L, + TC3589x_DRIVE2_H, + TC3589x_DRIVE3, + TC3589x_GPIODATA0, + TC3589x_GPIOMASK0, + TC3589x_GPIODATA1, + TC3589x_GPIOMASK1, + TC3589x_GPIODATA2, + TC3589x_GPIOMASK2, + TC3589x_GPIODIR0, + TC3589x_GPIODIR1, + TC3589x_GPIODIR2, + TC3589x_GPIOIE0, + TC3589x_GPIOIE1, + TC3589x_GPIOIE2, + TC3589x_RSTCTRL, + TC3589x_CLKCFG, +}; + +static u8 sleep_regs_val[] = { + 0x00, /* TC3589x_IOPC0_L */ + 0x00, /* TC3589x_IOPC0_H */ + 0x00, /* TC3589x_IOPC1_L */ + 0x00, /* TC3589x_IOPC1_H */ + 0x00, /* TC3589x_IOPC2_L */ + 0x00, /* TC3589x_IOPC2_H */ + 0xff, /* TC3589x_DRIVE0_L */ + 0xff, /* TC3589x_DRIVE0_H */ + 0xff, /* TC3589x_DRIVE1_L */ + 0xff, /* TC3589x_DRIVE1_H */ + 0xff, /* TC3589x_DRIVE2_L */ + 0xff, /* TC3589x_DRIVE2_H */ + 0x0f, /* TC3589x_DRIVE3 */ + 0x80, /* TC3589x_GPIODATA0 */ + 0x80, /* TC3589x_GPIOMASK0 */ + 0x80, /* TC3589x_GPIODATA1 */ + 0x80, /* TC3589x_GPIOMASK1 */ + 0x06, /* TC3589x_GPIODATA2 */ + 0x06, /* TC3589x_GPIOMASK2 */ + 0xf0, /* TC3589x_GPIODIR0 */ + 0xe0, /* TC3589x_GPIODIR1 */ + 0xee, /* TC3589x_GPIODIR2 */ + 0x0f, /* TC3589x_GPIOIE0 */ + 0x1f, /* TC3589x_GPIOIE1 */ + 0x11, /* TC3589x_GPIOIE2 */ + 0x0f, /* TC3589x_RSTCTRL */ + 0xb0 /* TC3589x_CLKCFG */ + +}; + +static u8 sleep_regs_backup[ARRAY_SIZE(sleep_regs)]; + static int tc3589x_suspend(struct device *dev) { struct tc3589x *tc3589x = dev_get_drvdata(dev); struct i2c_client *client = tc3589x->i2c; int ret = 0; + int i, j; + int val; + + /* Put the system to sleep mode */ + if (!device_may_wakeup(&client->dev)) { + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + val = tc3589x_reg_read(tc3589x, + sleep_regs[i]); + if (val < 0) + goto out; + + sleep_regs_backup[i] = (u8) (val & 0xff); + } - /* put the system to sleep mode */ - if (!device_may_wakeup(&client->dev)) - ret = tc3589x_reg_write(tc3589x, TC3589x_CLKMODE, - TC3589x_CLKMODE_MODCTL_SLEEP); + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + ret = tc3589x_reg_write(tc3589x, + sleep_regs[i], + sleep_regs_val[i]); + if (ret < 0) + goto fail; + + } + + ret = tc3589x_reg_write(tc3589x, + TC3589x_CLKMODE, + TC3589x_CLKMODE_MODCTL_SLEEP); + } else { + enable_irq_wake(client->irq); + } +out: + return ret; +fail: + for (j = 0; j <= i; j++) { + ret = tc3589x_reg_write(tc3589x, + sleep_regs[i], + sleep_regs_backup[i]); + if (ret < 0) + break; + } return ret; } @@ -376,12 +473,29 @@ static int tc3589x_resume(struct device *dev) struct tc3589x *tc3589x = dev_get_drvdata(dev); struct i2c_client *client = tc3589x->i2c; int ret = 0; + int i; - /* enable the system into operation */ + /* Enable the system into operation */ if (!device_may_wakeup(&client->dev)) - ret = tc3589x_reg_write(tc3589x, TC3589x_CLKMODE, - TC3589x_CLKMODE_MODCTL_OPERATION); - + { + ret = tc3589x_reg_write(tc3589x, + TC3589x_CLKMODE, + TC3589x_CLKMODE_MODCTL_OPERATION); + if (ret < 0) + goto out; + + for (i = ARRAY_SIZE(sleep_regs) - 1; i >= 0; i--) { + ret = tc3589x_reg_write(tc3589x, + sleep_regs[i], + sleep_regs_backup[i]); + /* Not much to do here if we fail */ + if (ret < 0) + break; + } + } else { + disable_irq_wake(client->irq); + } +out: return ret; } diff --git a/drivers/mfd/tps6105x.c b/drivers/mfd/tps6105x.c index a293b978e27..d7b9e0c60ea 100644 --- a/drivers/mfd/tps6105x.c +++ b/drivers/mfd/tps6105x.c @@ -195,6 +195,7 @@ static int __devinit tps6105x_probe(struct i2c_client *client, return 0; fail: + i2c_set_clientdata(client, NULL); kfree(tps6105x); return ret; } diff --git a/drivers/misc/ab8500-pwm.c b/drivers/misc/ab8500-pwm.c index 35903154ca2..372fd4a37f2 100644 --- a/drivers/misc/ab8500-pwm.c +++ b/drivers/misc/ab8500-pwm.c @@ -8,6 +8,7 @@ #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/pwm.h> +#include <linux/clk.h> #include <linux/mfd/ab8500.h> #include <linux/mfd/abx500.h> @@ -26,8 +27,10 @@ struct pwm_device { struct device *dev; struct list_head node; + struct clk *clk; const char *label; unsigned int pwm_id; + bool clk_enabled; }; static LIST_HEAD(pwm_list); @@ -66,9 +69,17 @@ int pwm_enable(struct pwm_device *pwm) { int ret; + if (!pwm->clk_enabled) { + ret = clk_enable(pwm->clk); + if (ret < 0) { + dev_err(pwm->dev, "failed to enable clock\n"); + return ret; + } + pwm->clk_enabled = true; + } ret = abx500_mask_and_set_register_interruptible(pwm->dev, AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, - 1 << (pwm->pwm_id-1), ENABLE_PWM); + 1 << (pwm->pwm_id-1), 1 << (pwm->pwm_id-1)); if (ret < 0) dev_err(pwm->dev, "%s: Failed to disable PWM, Error %d\n", pwm->label, ret); @@ -83,9 +94,27 @@ void pwm_disable(struct pwm_device *pwm) ret = abx500_mask_and_set_register_interruptible(pwm->dev, AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, 1 << (pwm->pwm_id-1), DISABLE_PWM); + /* + * Workaround to set PWM in disable. + * If enable bit is not toggled the PWM might output 50/50 duty cycle + * even though it should be disabled + */ + ret &= abx500_mask_and_set_register_interruptible(pwm->dev, + AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, + 1 << (pwm->pwm_id-1), + ENABLE_PWM << (pwm->pwm_id-1)); + ret &= abx500_mask_and_set_register_interruptible(pwm->dev, + AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, + 1 << (pwm->pwm_id-1), DISABLE_PWM); + if (ret < 0) dev_err(pwm->dev, "%s: Failed to disable PWM, Error %d\n", pwm->label, ret); + if (pwm->clk_enabled) { + clk_disable(pwm->clk); + pwm->clk_enabled = false; + } + return; } EXPORT_SYMBOL(pwm_disable); @@ -115,6 +144,8 @@ EXPORT_SYMBOL(pwm_free); static int __devinit ab8500_pwm_probe(struct platform_device *pdev) { struct pwm_device *pwm; + int ret = 0; + /* * Nothing to be done in probe, this is required to get the * device which is required for ab8500 read and write @@ -128,14 +159,24 @@ static int __devinit ab8500_pwm_probe(struct platform_device *pdev) pwm->pwm_id = pdev->id; list_add_tail(&pwm->node, &pwm_list); platform_set_drvdata(pdev, pwm); + + pwm->clk = clk_get(pwm->dev, NULL); + if (IS_ERR(pwm->clk)) { + dev_err(pwm->dev, "clock request failed\n"); + ret = PTR_ERR(pwm->clk); + kfree(pwm); + return ret; + } + pwm->clk_enabled = false; dev_dbg(pwm->dev, "pwm probe successful\n"); - return 0; + return ret; } static int __devexit ab8500_pwm_remove(struct platform_device *pdev) { struct pwm_device *pwm = platform_get_drvdata(pdev); list_del(&pwm->node); + clk_put(pwm->clk); dev_dbg(&pdev->dev, "pwm driver removed\n"); kfree(pwm); return 0; diff --git a/drivers/misc/bh1780gli.c b/drivers/misc/bh1780gli.c index 82fe2d06782..060441599cb 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -18,10 +18,16 @@ * this program. If not, see <http://www.gnu.org/licenses/>. */ #include <linux/i2c.h> +#include <linux/err.h> #include <linux/slab.h> #include <linux/mutex.h> #include <linux/platform_device.h> #include <linux/delay.h> +#include <linux/regulator/consumer.h> + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif #define BH1780_REG_CONTROL 0x80 #define BH1780_REG_PARTID 0x8A @@ -39,11 +45,20 @@ struct bh1780_data { struct i2c_client *client; + struct regulator *regulator; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif int power_state; /* lock for sysfs operations */ struct mutex lock; }; +#ifdef CONFIG_HAS_EARLYSUSPEND +static void bh1780_early_suspend(struct early_suspend *ddata); +static void bh1780_late_resume(struct early_suspend *ddata); +#endif + static int bh1780_write(struct bh1780_data *ddata, u8 reg, u8 val, char *msg) { int ret = i2c_smbus_write_byte_data(ddata->client, reg, val); @@ -71,6 +86,9 @@ static ssize_t bh1780_show_lux(struct device *dev, struct bh1780_data *ddata = platform_get_drvdata(pdev); int lsb, msb; + if (ddata->power_state == BH1780_POFF) + return -EINVAL; + lsb = bh1780_read(ddata, BH1780_REG_DLOW, "DLOW"); if (lsb < 0) return lsb; @@ -88,13 +106,9 @@ static ssize_t bh1780_show_power_state(struct device *dev, { struct platform_device *pdev = to_platform_device(dev); struct bh1780_data *ddata = platform_get_drvdata(pdev); - int state; - - state = bh1780_read(ddata, BH1780_REG_CONTROL, "CONTROL"); - if (state < 0) - return state; - return sprintf(buf, "%d\n", state & BH1780_POWMASK); + /* we already maintain a sw state */ + return sprintf(buf, "%d\n", ddata->power_state); } static ssize_t bh1780_store_power_state(struct device *dev, @@ -103,7 +117,7 @@ static ssize_t bh1780_store_power_state(struct device *dev, { struct platform_device *pdev = to_platform_device(dev); struct bh1780_data *ddata = platform_get_drvdata(pdev); - unsigned long val; + long val; int error; error = strict_strtoul(buf, 0, &val); @@ -113,14 +127,24 @@ static ssize_t bh1780_store_power_state(struct device *dev, if (val < BH1780_POFF || val > BH1780_PON) return -EINVAL; + if (ddata->power_state == val) + return count; + mutex_lock(&ddata->lock); + if (ddata->power_state == BH1780_POFF) + regulator_enable(ddata->regulator); + error = bh1780_write(ddata, BH1780_REG_CONTROL, val, "CONTROL"); if (error < 0) { mutex_unlock(&ddata->lock); + regulator_disable(ddata->regulator); return error; } + if (val == BH1780_POFF) + regulator_disable(ddata->regulator); + msleep(BH1780_PON_DELAY); ddata->power_state = val; mutex_unlock(&ddata->lock); @@ -130,7 +154,7 @@ static ssize_t bh1780_store_power_state(struct device *dev, static DEVICE_ATTR(lux, S_IRUGO, bh1780_show_lux, NULL); -static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO, +static DEVICE_ATTR(power_state, S_IWUGO | S_IRUGO, bh1780_show_power_state, bh1780_store_power_state); static struct attribute *bh1780_attributes[] = { @@ -152,21 +176,42 @@ static int __devinit bh1780_probe(struct i2c_client *client, if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) { ret = -EIO; - goto err_op_failed; + return ret; } ddata = kzalloc(sizeof(struct bh1780_data), GFP_KERNEL); if (ddata == NULL) { + dev_err(&client->dev, "failed to alloc ddata\n"); ret = -ENOMEM; - goto err_op_failed; + return ret; } ddata->client = client; i2c_set_clientdata(client, ddata); + ddata->regulator = regulator_get(&client->dev, "vcc"); + if (IS_ERR(ddata->regulator)) { + dev_err(&client->dev, "failed to get regulator\n"); + ret = PTR_ERR(ddata->regulator); + goto free_ddata; + } + + regulator_enable(ddata->regulator); + ret = bh1780_read(ddata, BH1780_REG_PARTID, "PART ID"); - if (ret < 0) - goto err_op_failed; + if (ret < 0) { + dev_err(&client->dev, "failed to read part ID\n"); + goto put_regulator; + } +#ifdef CONFIG_HAS_EARLYSUSPEND + ddata->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + ddata->early_suspend.suspend = bh1780_early_suspend; + ddata->early_suspend.resume = bh1780_late_resume; + register_early_suspend(&ddata->early_suspend); +#endif + + regulator_disable(ddata->regulator); + ddata->power_state = BH1780_POFF; dev_info(&client->dev, "Ambient Light Sensor, Rev : %d\n", (ret & BH1780_REVMASK)); @@ -175,11 +220,14 @@ static int __devinit bh1780_probe(struct i2c_client *client, ret = sysfs_create_group(&client->dev.kobj, &bh1780_attr_group); if (ret) - goto err_op_failed; + goto put_regulator; return 0; -err_op_failed: +put_regulator: + regulator_disable(ddata->regulator); + regulator_put(ddata->regulator); +free_ddata: kfree(ddata); return ret; } @@ -195,50 +243,106 @@ static int __devexit bh1780_remove(struct i2c_client *client) return 0; } -#ifdef CONFIG_PM -static int bh1780_suspend(struct device *dev) +#if defined(CONFIG_HAS_EARLYSUSPEND) || defined(CONFIG_PM) +static int bh1780_do_suspend(struct bh1780_data *ddata) { - struct bh1780_data *ddata; - int state, ret; - struct i2c_client *client = to_i2c_client(dev); + int ret = 0; - ddata = i2c_get_clientdata(client); - state = bh1780_read(ddata, BH1780_REG_CONTROL, "CONTROL"); - if (state < 0) - return state; + mutex_lock(&ddata->lock); - ddata->power_state = state & BH1780_POWMASK; + if (ddata->power_state == BH1780_POFF) + goto unlock; - ret = bh1780_write(ddata, BH1780_REG_CONTROL, BH1780_POFF, - "CONTROL"); + ret = bh1780_write(ddata, BH1780_REG_CONTROL, BH1780_POFF, "CONTROL"); if (ret < 0) - return ret; + goto unlock; - return 0; + if (ddata->regulator) + regulator_disable(ddata->regulator); +unlock: + mutex_unlock(&ddata->lock); + return ret; } -static int bh1780_resume(struct device *dev) +static int bh1780_do_resume(struct bh1780_data *ddata) { - struct bh1780_data *ddata; - int state, ret; - struct i2c_client *client = to_i2c_client(dev); + int ret = 0; - ddata = i2c_get_clientdata(client); - state = ddata->power_state; - ret = bh1780_write(ddata, BH1780_REG_CONTROL, state, - "CONTROL"); + mutex_lock(&ddata->lock); + + if (ddata->power_state == BH1780_POFF) + goto unlock; + + if (ddata->regulator) + regulator_enable(ddata->regulator); + ret = bh1780_write(ddata, BH1780_REG_CONTROL, + ddata->power_state, "CONTROL"); + +unlock: + mutex_unlock(&ddata->lock); + return ret; +} +#endif + +#ifndef CONFIG_HAS_EARLYSUSPEND +#ifdef CONFIG_PM +static int bh1780_suspend(struct device *dev) +{ + struct bh1780_data *ddata = dev_get_drvdata(dev); + int ret = 0; + + ret = bh1780_do_suspend(ddata); if (ret < 0) - return ret; + dev_err(&ddata->client->dev, + "Error while suspending the device\n"); - return 0; + return ret; +} + +static int bh1780_resume(struct device *dev) +{ + struct bh1780_data *ddata = dev_get_drvdata(dev); + int ret = 0; + + ret = bh1780_do_resume(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while resuming the device\n"); + + return ret; } + static SIMPLE_DEV_PM_OPS(bh1780_pm, bh1780_suspend, bh1780_resume); #define BH1780_PMOPS (&bh1780_pm) +#endif /* CONFIG_PM */ #else #define BH1780_PMOPS NULL -#endif /* CONFIG_PM */ +static void bh1780_early_suspend(struct early_suspend *data) +{ + struct bh1780_data *ddata = + container_of(data, struct bh1780_data, early_suspend); + int ret; + + ret = bh1780_do_suspend(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while suspending the device\n"); +} + +static void bh1780_late_resume(struct early_suspend *data) +{ + struct bh1780_data *ddata = + container_of(data, struct bh1780_data, early_suspend); + int ret; + + ret = bh1780_do_resume(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while resuming the device\n"); +} +#endif /*!CONFIG_HAS_EARLYSUSPEND */ static const struct i2c_device_id bh1780_id[] = { { "bh1780", 0 }, @@ -251,8 +355,10 @@ static struct i2c_driver bh1780_driver = { .id_table = bh1780_id, .driver = { .name = "bh1780", +#if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) .pm = BH1780_PMOPS, -}, +#endif + }, }; static int __init bh1780_init(void) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 4c1a648d00f..6be812ca3fb 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -94,6 +94,11 @@ struct mmc_blk_data { unsigned int read_only; unsigned int part_type; unsigned int name_idx; + unsigned int reset_done; +#define MMC_BLK_READ BIT(0) +#define MMC_BLK_WRITE BIT(1) +#define MMC_BLK_DISCARD BIT(2) +#define MMC_BLK_SECDISCARD BIT(3) /* * Only set in main mmc_blk_data associated @@ -102,6 +107,8 @@ struct mmc_blk_data { */ unsigned int part_curr; struct device_attribute force_ro; + struct device_attribute boot_partition_ro_lock; + int area_type; }; static DEFINE_MUTEX(open_lock); @@ -109,11 +116,11 @@ static DEFINE_MUTEX(open_lock); enum mmc_blk_status { MMC_BLK_SUCCESS = 0, MMC_BLK_PARTIAL, - MMC_BLK_RETRY, - MMC_BLK_RETRY_SINGLE, - MMC_BLK_DATA_ERR, MMC_BLK_CMD_ERR, + MMC_BLK_RETRY, MMC_BLK_ABORT, + MMC_BLK_DATA_ERR, + MMC_BLK_ECC_ERR, }; module_param(perdev_minors, int, 0444); @@ -160,6 +167,72 @@ static void mmc_blk_put(struct mmc_blk_data *md) mutex_unlock(&open_lock); } +#define EXT_CSD_BOOT_WP_PWR_WP_TEXT "pwr_ro" +#define EXT_CSD_BOOT_WP_PERM_WP_TEXT "perm_ro" +#define EXT_CSD_BOOT_WP_WP_DISABLED_TEXT "rw" +static ssize_t boot_partition_ro_lock_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + struct mmc_card *card = md->queue.card; + const char *out_text; + + if (card->ext_csd.boot_locked + & EXT_CSD_BOOT_WP_B_PERM_WP_EN) + out_text = EXT_CSD_BOOT_WP_PERM_WP_TEXT; + else if (card->ext_csd.boot_locked + & EXT_CSD_BOOT_WP_B_PWR_WP_EN) + out_text = EXT_CSD_BOOT_WP_PWR_WP_TEXT; + else + out_text = EXT_CSD_BOOT_WP_WP_DISABLED_TEXT; + + ret = snprintf(buf, PAGE_SIZE, "%s\n", out_text); + + return ret; +} + +static ssize_t boot_partition_ro_lock_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret; + struct mmc_blk_data *md; + struct mmc_card *card; + u8 set = 0; + + md = mmc_blk_get(dev_to_disk(dev)); + card = md->queue.card; + + if (!strncmp(buf, EXT_CSD_BOOT_WP_PWR_WP_TEXT, + strlen(EXT_CSD_BOOT_WP_PWR_WP_TEXT))) + set = EXT_CSD_BOOT_WP_B_PWR_WP_EN; + else if (!strncmp(buf, EXT_CSD_BOOT_WP_PERM_WP_TEXT, + strlen(EXT_CSD_BOOT_WP_PERM_WP_TEXT))) + set = EXT_CSD_BOOT_WP_B_PERM_WP_EN; + + if (set) { + mmc_claim_host(card->host); + + ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BOOT_WP, + set, + card->ext_csd.part_time); + if (ret) + pr_err("Boot Partition Lock failed: %d\n", ret); + else + card->ext_csd.boot_locked = set; + + mmc_release_host(card->host); + + if (!ret) + set_disk_ro(md->disk, 1); + } + ret = count; + + mmc_blk_put(md); + return ret; +} + static ssize_t force_ro_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -291,7 +364,7 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev, struct mmc_card *card; struct mmc_command cmd = {0}; struct mmc_data data = {0}; - struct mmc_request mrq = {0}; + struct mmc_request mrq = {NULL}; struct scatterlist sg; int err; @@ -442,19 +515,24 @@ static inline int mmc_blk_part_switch(struct mmc_card *card, { int ret; struct mmc_blk_data *main_md = mmc_get_drvdata(card); + if (main_md->part_curr == md->part_type) return 0; if (mmc_card_mmc(card)) { - card->ext_csd.part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK; - card->ext_csd.part_config |= md->part_type; + u8 part_config = card->ext_csd.part_config; + + part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK; + part_config |= md->part_type; ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_PART_CONFIG, card->ext_csd.part_config, + EXT_CSD_PART_CONFIG, part_config, card->ext_csd.part_time); if (ret) return ret; -} + + card->ext_csd.part_config = part_config; + } main_md->part_curr = md->part_type; return 0; @@ -466,7 +544,7 @@ static u32 mmc_sd_num_wr_blocks(struct mmc_card *card) u32 result; __be32 *blocks; - struct mmc_request mrq = {0}; + struct mmc_request mrq = {NULL}; struct mmc_command cmd = {0}; struct mmc_data data = {0}; unsigned int timeout_us; @@ -616,7 +694,7 @@ static int mmc_blk_cmd_error(struct request *req, const char *name, int error, * Otherwise we don't understand what happened, so abort. */ static int mmc_blk_cmd_recovery(struct mmc_card *card, struct request *req, - struct mmc_blk_request *brq) + struct mmc_blk_request *brq, int *ecc_err) { bool prev_cmd_status_valid = true; u32 status, stop_status = 0; @@ -641,6 +719,12 @@ static int mmc_blk_cmd_recovery(struct mmc_card *card, struct request *req, if (err) return ERR_ABORT; + /* Flag ECC errors */ + if ((status & R1_CARD_ECC_FAILED) || + (brq->stop.resp[0] & R1_CARD_ECC_FAILED) || + (brq->cmd.resp[0] & R1_CARD_ECC_FAILED)) + *ecc_err = 1; + /* * Check the current card state. If it is in some data transfer * mode, tell it to stop (and hopefully transition back to TRAN.) @@ -658,6 +742,8 @@ static int mmc_blk_cmd_recovery(struct mmc_card *card, struct request *req, */ if (err) return ERR_ABORT; + if (stop_status & R1_CARD_ECC_FAILED) + *ecc_err = 1; } /* Check for set block count errors */ @@ -670,6 +756,10 @@ static int mmc_blk_cmd_recovery(struct mmc_card *card, struct request *req, return mmc_blk_cmd_error(req, "r/w cmd", brq->cmd.error, prev_cmd_status_valid, status); + /* Data errors */ + if (!brq->stop.error) + return ERR_CONTINUE; + /* Now for stop errors. These aren't fatal to the transfer. */ pr_err("%s: error %d sending stop command, original cmd response %#x, card status %#x\n", req->rq_disk->disk_name, brq->stop.error, @@ -686,12 +776,45 @@ static int mmc_blk_cmd_recovery(struct mmc_card *card, struct request *req, return ERR_CONTINUE; } +static int mmc_blk_reset(struct mmc_blk_data *md, struct mmc_host *host, + int type) +{ + int err; + + if (md->reset_done & type) + return -EEXIST; + + md->reset_done |= type; + err = mmc_hw_reset(host); + /* Ensure we switch back to the correct partition */ + if (err != -EOPNOTSUPP) { + struct mmc_blk_data *main_md = mmc_get_drvdata(host->card); + int part_err; + + main_md->part_curr = main_md->part_type; + part_err = mmc_blk_part_switch(host->card, md); + if (part_err) { + /* + * We have failed to get back into the correct + * partition, so we need to abort the whole request. + */ + return -ENODEV; + } + } + return err; +} + +static inline void mmc_blk_reset_success(struct mmc_blk_data *md, int type) +{ + md->reset_done &= ~type; +} + static int mmc_blk_issue_discard_rq(struct mmc_queue *mq, struct request *req) { struct mmc_blk_data *md = mq->data; struct mmc_card *card = md->queue.card; unsigned int from, nr, arg; - int err = 0; + int err = 0, type = MMC_BLK_DISCARD; if (!mmc_can_erase(card)) { err = -EOPNOTSUPP; @@ -705,7 +828,7 @@ static int mmc_blk_issue_discard_rq(struct mmc_queue *mq, struct request *req) arg = MMC_TRIM_ARG; else arg = MMC_ERASE_ARG; - +retry: if (card->quirks & MMC_QUIRK_INAND_CMD38) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, INAND_CMD38_ARG_EXT_CSD, @@ -718,6 +841,10 @@ static int mmc_blk_issue_discard_rq(struct mmc_queue *mq, struct request *req) } err = mmc_erase(card, from, nr, arg); out: + if (err == -EIO && !mmc_blk_reset(md, card->host, type)) + goto retry; + if (!err) + mmc_blk_reset_success(md, type); spin_lock_irq(&md->lock); __blk_end_request(req, err, blk_rq_bytes(req)); spin_unlock_irq(&md->lock); @@ -731,7 +858,7 @@ static int mmc_blk_issue_secdiscard_rq(struct mmc_queue *mq, struct mmc_blk_data *md = mq->data; struct mmc_card *card = md->queue.card; unsigned int from, nr, arg; - int err = 0; + int err = 0, type = MMC_BLK_SECDISCARD; if (!mmc_can_secure_erase_trim(card)) { err = -EOPNOTSUPP; @@ -745,7 +872,7 @@ static int mmc_blk_issue_secdiscard_rq(struct mmc_queue *mq, arg = MMC_SECURE_TRIM1_ARG; else arg = MMC_SECURE_ERASE_ARG; - +retry: if (card->quirks & MMC_QUIRK_INAND_CMD38) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, INAND_CMD38_ARG_EXT_CSD, @@ -769,6 +896,10 @@ static int mmc_blk_issue_secdiscard_rq(struct mmc_queue *mq, err = mmc_erase(card, from, nr, MMC_SECURE_TRIM2_ARG); } out: + if (err == -EIO && !mmc_blk_reset(md, card->host, type)) + goto retry; + if (!err) + mmc_blk_reset_success(md, type); spin_lock_irq(&md->lock); __blk_end_request(req, err, blk_rq_bytes(req)); spin_unlock_irq(&md->lock); @@ -825,11 +956,11 @@ static inline void mmc_apply_rel_rw(struct mmc_blk_request *brq, static int mmc_blk_err_check(struct mmc_card *card, struct mmc_async_req *areq) { - enum mmc_blk_status ret = MMC_BLK_SUCCESS; struct mmc_queue_req *mq_mrq = container_of(areq, struct mmc_queue_req, mmc_active); struct mmc_blk_request *brq = &mq_mrq->brq; struct request *req = mq_mrq->req; + int ecc_err = 0; /* * sbc.error indicates a problem with the set block count @@ -841,8 +972,9 @@ static int mmc_blk_err_check(struct mmc_card *card, * stop.error indicates a problem with the stop command. Data * may have been transferred, or may still be transferring. */ - if (brq->sbc.error || brq->cmd.error || brq->stop.error) { - switch (mmc_blk_cmd_recovery(card, req, brq)) { + if (brq->sbc.error || brq->cmd.error || brq->stop.error || + brq->data.error) { + switch (mmc_blk_cmd_recovery(card, req, brq, &ecc_err)) { case ERR_RETRY: return MMC_BLK_RETRY; case ERR_ABORT: @@ -894,23 +1026,21 @@ static int mmc_blk_err_check(struct mmc_card *card, brq->cmd.resp[0], brq->stop.resp[0]); if (rq_data_dir(req) == READ) { - if (brq->data.blocks > 1) { - /* Redo read one sector at a time */ - pr_warning("%s: retrying using single block read\n", - req->rq_disk->disk_name); - return MMC_BLK_RETRY_SINGLE; - } + if (ecc_err) + return MMC_BLK_ECC_ERR; return MMC_BLK_DATA_ERR; } else { return MMC_BLK_CMD_ERR; } } - if (ret == MMC_BLK_SUCCESS && - blk_rq_bytes(req) != brq->data.bytes_xfered) - ret = MMC_BLK_PARTIAL; + if (!brq->data.bytes_xfered) + return MMC_BLK_RETRY; - return ret; + if (blk_rq_bytes(req) != brq->data.bytes_xfered) + return MMC_BLK_PARTIAL; + + return MMC_BLK_SUCCESS; } static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, @@ -1049,12 +1179,41 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, mmc_queue_bounce_pre(mqrq); } +static int mmc_blk_cmd_err(struct mmc_blk_data *md, struct mmc_card *card, + struct mmc_blk_request *brq, struct request *req, + int ret) +{ + /* + * If this is an SD card and we're writing, we can first + * mark the known good sectors as ok. + * + * If the card is not SD, we can still ok written sectors + * as reported by the controller (which might be less than + * the real number of written sectors, but never more). + */ + if (mmc_card_sd(card)) { + u32 blocks; + + blocks = mmc_sd_num_wr_blocks(card); + if (blocks != (u32)-1) { + spin_lock_irq(&md->lock); + ret = __blk_end_request(req, 0, blocks << 9); + spin_unlock_irq(&md->lock); + } + } else { + spin_lock_irq(&md->lock); + ret = __blk_end_request(req, 0, brq->data.bytes_xfered); + spin_unlock_irq(&md->lock); + } + return ret; +} + static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) { struct mmc_blk_data *md = mq->data; struct mmc_card *card = md->queue.card; struct mmc_blk_request *brq = &mq->mqrq_cur->brq; - int ret = 1, disable_multi = 0, retry = 0; + int ret = 1, disable_multi = 0, retry = 0, type; enum mmc_blk_status status; struct mmc_queue_req *mq_rq; struct request *req; @@ -1076,6 +1235,7 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) mq_rq = container_of(areq, struct mmc_queue_req, mmc_active); brq = &mq_rq->brq; req = mq_rq->req; + type = rq_data_dir(req) == READ ? MMC_BLK_READ : MMC_BLK_WRITE; mmc_queue_bounce_post(mq_rq); switch (status) { @@ -1084,17 +1244,17 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) /* * A block was successfully transferred. */ + mmc_blk_reset_success(md, type); spin_lock_irq(&md->lock); ret = __blk_end_request(req, 0, brq->data.bytes_xfered); spin_unlock_irq(&md->lock); + /* + * If the blk_end_request function returns non-zero even + * though all data has been transferred and no errors + * were returned by the host controller, it's a bug. + */ if (status == MMC_BLK_SUCCESS && ret) { - /* - * The blk_end_request has returned non zero - * even though all data is transfered and no - * erros returned by host. - * If this happen it's a bug. - */ printk(KERN_ERR "%s BUG rq_tot %d d_xfer %d\n", __func__, blk_rq_bytes(req), brq->data.bytes_xfered); @@ -1103,16 +1263,36 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) } break; case MMC_BLK_CMD_ERR: - goto cmd_err; - case MMC_BLK_RETRY_SINGLE: - disable_multi = 1; - break; + ret = mmc_blk_cmd_err(md, card, brq, req, ret); + if (!mmc_blk_reset(md, card->host, type)) + break; + goto cmd_abort; case MMC_BLK_RETRY: if (retry++ < 5) break; + /* Fall through */ case MMC_BLK_ABORT: + if (!mmc_blk_reset(md, card->host, type)) + break; goto cmd_abort; - case MMC_BLK_DATA_ERR: + case MMC_BLK_DATA_ERR: { + int err; + + err = mmc_blk_reset(md, card->host, type); + if (!err) + break; + if (err == -ENODEV) + goto cmd_abort; + /* Fall through */ + } + case MMC_BLK_ECC_ERR: + if (brq->data.blocks > 1) { + /* Redo read one sector at a time */ + pr_warning("%s: retrying using single block read\n", + req->rq_disk->disk_name); + disable_multi = 1; + break; + } /* * After an error, we redo I/O one sector at a * time, so we only reach here after trying to @@ -1129,7 +1309,7 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) if (ret) { /* - * In case of a none complete request + * In case of a incomplete request * prepare it again and resend. */ mmc_blk_rw_rq_prep(mq_rq, card, disable_multi, mq); @@ -1139,30 +1319,6 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) return 1; - cmd_err: - /* - * If this is an SD card and we're writing, we can first - * mark the known good sectors as ok. - * - * If the card is not SD, we can still ok written sectors - * as reported by the controller (which might be less than - * the real number of written sectors, but never more). - */ - if (mmc_card_sd(card)) { - u32 blocks; - - blocks = mmc_sd_num_wr_blocks(card); - if (blocks != (u32)-1) { - spin_lock_irq(&md->lock); - ret = __blk_end_request(req, 0, blocks << 9); - spin_unlock_irq(&md->lock); - } - } else { - spin_lock_irq(&md->lock); - ret = __blk_end_request(req, 0, brq->data.bytes_xfered); - spin_unlock_irq(&md->lock); - } - cmd_abort: spin_lock_irq(&md->lock); while (ret) @@ -1190,6 +1346,11 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) ret = mmc_blk_part_switch(card, md); if (ret) { + if (req) { + spin_lock_irq(&md->lock); + __blk_end_request_all(req, -EIO); + spin_unlock_irq(&md->lock); + } ret = 0; goto out; } @@ -1228,7 +1389,8 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, struct device *parent, sector_t size, bool default_ro, - const char *subname) + const char *subname, + int area_type) { struct mmc_blk_data *md; int devidx, ret; @@ -1253,10 +1415,11 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, if (!subname) { md->name_idx = find_first_zero_bit(name_use, max_devices); __set_bit(md->name_idx, name_use); - } - else + } else { md->name_idx = ((struct mmc_blk_data *) dev_to_disk(parent)->private_data)->name_idx; + md->area_type = area_type; + } /* * Set the read-only status based on the supported commands @@ -1351,7 +1514,7 @@ static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card) size = card->csd.capacity << (card->csd.read_blkbits - 9); } - md = mmc_blk_alloc_req(card, &card->dev, size, false, NULL); + md = mmc_blk_alloc_req(card, &card->dev, size, false, NULL, false); return md; } @@ -1360,13 +1523,14 @@ static int mmc_blk_alloc_part(struct mmc_card *card, unsigned int part_type, sector_t size, bool default_ro, - const char *subname) + const char *subname, + int area_type) { char cap_str[10]; struct mmc_blk_data *part_md; part_md = mmc_blk_alloc_req(card, disk_to_dev(md->disk), size, default_ro, - subname); + subname, area_type); if (IS_ERR(part_md)) return PTR_ERR(part_md); part_md->part_type = part_type; @@ -1380,26 +1544,30 @@ static int mmc_blk_alloc_part(struct mmc_card *card, return 0; } +/* MMC Physical partitions consist of two boot partitions and + * up to four general purpose partitions. + * For each partition enabled in EXT_CSD a block device will be allocatedi + * to provide access to the partition. + */ + static int mmc_blk_alloc_parts(struct mmc_card *card, struct mmc_blk_data *md) { - int ret = 0; + int idx, ret = 0; if (!mmc_card_mmc(card)) return 0; - if (card->ext_csd.boot_size) { - ret = mmc_blk_alloc_part(card, md, EXT_CSD_PART_CONFIG_ACC_BOOT0, - card->ext_csd.boot_size >> 9, - true, - "boot0"); - if (ret) - return ret; - ret = mmc_blk_alloc_part(card, md, EXT_CSD_PART_CONFIG_ACC_BOOT1, - card->ext_csd.boot_size >> 9, - true, - "boot1"); - if (ret) - return ret; + for (idx = 0; idx < card->nr_parts; idx++) { + if (card->part[idx].size) { + ret = mmc_blk_alloc_part(card, md, + card->part[idx].part_cfg, + card->part[idx].size >> 9, + card->part[idx].force_ro, + card->part[idx].name, + card->part[idx].area_type); + if (ret) + return ret; + } } return ret; @@ -1428,6 +1596,9 @@ static void mmc_blk_remove_req(struct mmc_blk_data *md) if (md) { if (md->disk->flags & GENHD_FL_UP) { device_remove_file(disk_to_dev(md->disk), &md->force_ro); + if (md->area_type == MMC_BLK_DATA_AREA_BOOT) + device_remove_file(disk_to_dev(md->disk), + &md->boot_partition_ro_lock); /* Stop new requests from getting into the queue */ del_gendisk(md->disk); @@ -1465,7 +1636,24 @@ static int mmc_add_disk(struct mmc_blk_data *md) md->force_ro.attr.mode = S_IRUGO | S_IWUSR; ret = device_create_file(disk_to_dev(md->disk), &md->force_ro); if (ret) - del_gendisk(md->disk); + goto force_ro_fail; + + if (md->area_type == MMC_BLK_DATA_AREA_BOOT) { + md->boot_partition_ro_lock.show = boot_partition_ro_lock_show; + md->boot_partition_ro_lock.store = boot_partition_ro_lock_store; + md->boot_partition_ro_lock.attr.name = "boot_partition_ro_lock"; + md->boot_partition_ro_lock.attr.mode = S_IRUGO | S_IWUSR; + ret = device_create_file(disk_to_dev(md->disk), + &md->boot_partition_ro_lock); + if (ret) + goto boot_partition_ro_lock_fail; + } + return ret; + +boot_partition_ro_lock_fail: + device_remove_file(disk_to_dev(md->disk), &md->force_ro); +force_ro_fail: + del_gendisk(md->disk); return ret; } diff --git a/drivers/mmc/card/mmc_test.c b/drivers/mmc/card/mmc_test.c index 2bf229acd3b..9cdce636713 100644 --- a/drivers/mmc/card/mmc_test.c +++ b/drivers/mmc/card/mmc_test.c @@ -2328,6 +2328,31 @@ static int mmc_test_profile_sglen_r_nonblock_perf(struct mmc_test_card *test) return mmc_test_rw_multiple_sg_len(test, &test_data); } +/* + * eMMC hardware reset. + */ +static int mmc_test_hw_reset(struct mmc_test_card *test) +{ + struct mmc_card *card = test->card; + struct mmc_host *host = card->host; + int err; + + err = mmc_hw_reset_check(host); + if (!err) + return RESULT_OK; + + if (err == -ENOSYS) + return RESULT_FAIL; + + if (err != -EOPNOTSUPP) + return err; + + if (!mmc_can_reset(card)) + return RESULT_UNSUP_CARD; + + return RESULT_UNSUP_HOST; +} + static const struct mmc_test_case mmc_test_cases[] = { { .name = "Basic write (no data verification)", @@ -2650,6 +2675,11 @@ static const struct mmc_test_case mmc_test_cases[] = { .run = mmc_test_profile_sglen_r_nonblock_perf, .cleanup = mmc_test_area_cleanup, }, + + { + .name = "eMMC hardware reset", + .run = mmc_test_hw_reset, + }, }; static DEFINE_MUTEX(mmc_test_lock); diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 45fb362e3f0..5196312bb55 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -108,7 +108,7 @@ static void mmc_request(struct request_queue *q) wake_up_process(mq->thread); } -struct scatterlist *mmc_alloc_sg(int sg_len, int *err) +static struct scatterlist *mmc_alloc_sg(int sg_len, int *err) { struct scatterlist *sg; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index b27b94078c2..ec769490300 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -24,6 +24,8 @@ #include <linux/regulator/consumer.h> #include <linux/pm_runtime.h> #include <linux/suspend.h> +#include <linux/fault-inject.h> +#include <linux/random.h> #include <linux/mmc/card.h> #include <linux/mmc/host.h> @@ -83,6 +85,43 @@ static void mmc_flush_scheduled_work(void) flush_workqueue(workqueue); } +#ifdef CONFIG_FAIL_MMC_REQUEST + +/* + * Internal function. Inject random data errors. + * If mmc_data is NULL no errors are injected. + */ +static void mmc_should_fail_request(struct mmc_host *host, + struct mmc_request *mrq) +{ + struct mmc_command *cmd = mrq->cmd; + struct mmc_data *data = mrq->data; + static const int data_errors[] = { + -ETIMEDOUT, + -EILSEQ, + -EIO, + }; + + if (!data) + return; + + if (cmd->error || data->error || + !should_fail(&host->fail_mmc_request, data->blksz * data->blocks)) + return; + + data->error = data_errors[random32() % ARRAY_SIZE(data_errors)]; + data->bytes_xfered = (random32() % (data->bytes_xfered >> 9)) << 9; +} + +#else /* CONFIG_FAIL_MMC_REQUEST */ + +static inline void mmc_should_fail_request(struct mmc_host *host, + struct mmc_request *mrq) +{ +} + +#endif /* CONFIG_FAIL_MMC_REQUEST */ + /** * mmc_request_done - finish processing an MMC request * @host: MMC host which completed request @@ -102,13 +141,15 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) } if (err && cmd->retries) { - pr_debug("%s: req failed (CMD%u): %d, retrying...\n", - mmc_hostname(host), cmd->opcode, err); - - cmd->retries--; - cmd->error = 0; - host->ops->request(host, mrq); + /* + * Request starter must handle retries - see + * mmc_wait_for_req_done(). + */ + if (mrq->done) + mrq->done(mrq); } else { + mmc_should_fail_request(host, mrq); + led_trigger_event(host->led, LED_OFF); pr_debug("%s: req done (CMD%u): %d: %08x %08x %08x %08x\n", @@ -212,7 +253,21 @@ static void __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq) static void mmc_wait_for_req_done(struct mmc_host *host, struct mmc_request *mrq) { - wait_for_completion(&mrq->completion); + struct mmc_command *cmd; + + while (1) { + wait_for_completion(&mrq->completion); + + cmd = mrq->cmd; + if (!cmd->error || !cmd->retries) + break; + + pr_debug("%s: req failed (CMD%u): %d, retrying...\n", + mmc_hostname(host), cmd->opcode, cmd->error); + cmd->retries--; + cmd->error = 0; + host->ops->request(host, mrq); + } } /** @@ -279,8 +334,14 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, mmc_wait_for_req_done(host, host->areq->mrq); err = host->areq->err_check(host->card, host->areq); if (err) { + /* post process the completed failed request */ mmc_post_req(host, host->areq->mrq, 0); if (areq) + /* + * Cancel the new prepared request, because + * it can't run until the failed + * request has been properly handled. + */ mmc_post_req(host, areq->mrq, -EINVAL); host->areq = NULL; @@ -330,7 +391,7 @@ EXPORT_SYMBOL(mmc_wait_for_req); */ int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries) { - struct mmc_request mrq = {0}; + struct mmc_request mrq = {NULL}; WARN_ON(!host->claimed); @@ -1119,13 +1180,11 @@ static void mmc_power_up(struct mmc_host *host) bit = fls(host->ocr_avail) - 1; host->ios.vdd = bit; - if (mmc_host_is_spi(host)) { + if (mmc_host_is_spi(host)) host->ios.chip_select = MMC_CS_HIGH; - host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; - } else { + else host->ios.chip_select = MMC_CS_DONTCARE; - host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; - } + host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; host->ios.power_mode = MMC_POWER_UP; host->ios.bus_width = MMC_BUS_WIDTH_1; host->ios.timing = MMC_TIMING_LEGACY; @@ -1151,7 +1210,7 @@ static void mmc_power_up(struct mmc_host *host) mmc_host_clk_release(host); } -static void mmc_power_off(struct mmc_host *host) +void mmc_power_off(struct mmc_host *host) { mmc_host_clk_hold(host); @@ -1173,6 +1232,13 @@ static void mmc_power_off(struct mmc_host *host) host->ios.timing = MMC_TIMING_LEGACY; mmc_set_ios(host); + /* + * Some configurations, such as the 802.11 SDIO card in the OLPC + * XO-1.5, require a short delay after poweroff before the card + * can be successfully turned on again. + */ + mmc_delay(1); + mmc_host_clk_release(host); } @@ -1241,8 +1307,7 @@ void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops) } /* - * Remove the current bus handler from a host. Assumes that there are - * no interesting cards left, so the bus is powered down. + * Remove the current bus handler from a host. */ void mmc_detach_bus(struct mmc_host *host) { @@ -1259,8 +1324,6 @@ void mmc_detach_bus(struct mmc_host *host) spin_unlock_irqrestore(&host->lock, flags); - mmc_power_off(host); - mmc_bus_put(host); } @@ -1480,7 +1543,7 @@ static int mmc_do_erase(struct mmc_card *card, unsigned int from, if (err) { printk(KERN_ERR "mmc_erase: group start error %d, " "status %#x\n", err, cmd.resp[0]); - err = -EINVAL; + err = -EIO; goto out; } @@ -1495,7 +1558,7 @@ static int mmc_do_erase(struct mmc_card *card, unsigned int from, if (err) { printk(KERN_ERR "mmc_erase: group end error %d, status %#x\n", err, cmd.resp[0]); - err = -EINVAL; + err = -EIO; goto out; } @@ -1727,6 +1790,94 @@ int mmc_set_blocklen(struct mmc_card *card, unsigned int blocklen) } EXPORT_SYMBOL(mmc_set_blocklen); +static void mmc_hw_reset_for_init(struct mmc_host *host) +{ + if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset) + return; + mmc_host_clk_hold(host); + host->ops->hw_reset(host); + mmc_host_clk_release(host); +} + +int mmc_can_reset(struct mmc_card *card) +{ + u8 rst_n_function; + + if (!mmc_card_mmc(card)) + return 0; + rst_n_function = card->ext_csd.rst_n_function; + if ((rst_n_function & EXT_CSD_RST_N_EN_MASK) != EXT_CSD_RST_N_ENABLED) + return 0; + return 1; +} +EXPORT_SYMBOL(mmc_can_reset); + +static int mmc_do_hw_reset(struct mmc_host *host, int check) +{ + struct mmc_card *card = host->card; + + if (!host->bus_ops->power_restore) + return -EOPNOTSUPP; + + if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset) + return -EOPNOTSUPP; + + if (!card) + return -EINVAL; + + if (!mmc_can_reset(card)) + return -EOPNOTSUPP; + + mmc_host_clk_hold(host); + mmc_set_clock(host, host->f_init); + + host->ops->hw_reset(host); + + /* If the reset has happened, then a status command will fail */ + if (check) { + struct mmc_command cmd = {0}; + int err; + + cmd.opcode = MMC_SEND_STATUS; + if (!mmc_host_is_spi(card->host)) + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC; + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (!err) { + mmc_host_clk_release(host); + return -ENOSYS; + } + } + + host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_DDR); + if (mmc_host_is_spi(host)) { + host->ios.chip_select = MMC_CS_HIGH; + host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; + } else { + host->ios.chip_select = MMC_CS_DONTCARE; + host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + } + host->ios.bus_width = MMC_BUS_WIDTH_1; + host->ios.timing = MMC_TIMING_LEGACY; + mmc_set_ios(host); + + mmc_host_clk_release(host); + + return host->bus_ops->power_restore(host); +} + +int mmc_hw_reset(struct mmc_host *host) +{ + return mmc_do_hw_reset(host, 0); +} +EXPORT_SYMBOL(mmc_hw_reset); + +int mmc_hw_reset_check(struct mmc_host *host) +{ + return mmc_do_hw_reset(host, 1); +} +EXPORT_SYMBOL(mmc_hw_reset_check); + static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) { host->f_init = freq; @@ -1738,6 +1889,12 @@ static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) mmc_power_up(host); /* + * Some eMMCs (with VCCQ always on) may not be reset after power up, so + * do a hardware reset if possible. + */ + mmc_hw_reset_for_init(host); + + /* * sdio_reset sends CMD52 to reset card. Since we do not know * if the card is being re-initialized, just send it. CMD52 * should be ignored by SD/eMMC cards. @@ -1845,6 +2002,7 @@ void mmc_stop_host(struct mmc_host *host) mmc_claim_host(host); mmc_detach_bus(host); + mmc_power_off(host); mmc_release_host(host); mmc_bus_put(host); return; @@ -1974,6 +2132,7 @@ int mmc_suspend_host(struct mmc_host *host) host->bus_ops->remove(host); mmc_claim_host(host); mmc_detach_bus(host); + mmc_power_off(host); mmc_release_host(host); host->pm_flags = 0; err = 0; @@ -2061,6 +2220,7 @@ int mmc_pm_notify(struct notifier_block *notify_block, host->bus_ops->remove(host); mmc_detach_bus(host); + mmc_power_off(host); mmc_release_host(host); host->pm_flags = 0; break; diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index d9411ed2a39..14664f1fb16 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -43,6 +43,7 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, bool cmd11); void mmc_set_timing(struct mmc_host *host, unsigned int timing); void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type); +void mmc_power_off(struct mmc_host *host); static inline void mmc_delay(unsigned int ms) { diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 998797ed67a..6045ea46936 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -7,11 +7,13 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ +#include <linux/moduleparam.h> #include <linux/debugfs.h> #include <linux/fs.h> #include <linux/seq_file.h> #include <linux/slab.h> #include <linux/stat.h> +#include <linux/fault-inject.h> #include <linux/mmc/card.h> #include <linux/mmc/host.h> @@ -19,6 +21,14 @@ #include "core.h" #include "mmc_ops.h" +#ifdef CONFIG_FAIL_MMC_REQUEST + +static DECLARE_FAULT_ATTR(fail_default_attr); +static char *fail_request; +module_param(fail_request, charp, 0); + +#endif /* CONFIG_FAIL_MMC_REQUEST */ + /* The debugfs functions are optimized away when CONFIG_DEBUG_FS isn't set. */ static int mmc_ios_show(struct seq_file *s, void *data) { @@ -113,6 +123,15 @@ static int mmc_ios_show(struct seq_file *s, void *data) case MMC_TIMING_SD_HS: str = "sd high-speed"; break; + case MMC_TIMING_UHS_SDR50: + str = "sd uhs SDR50"; + break; + case MMC_TIMING_UHS_SDR104: + str = "sd uhs SDR104"; + break; + case MMC_TIMING_UHS_DDR50: + str = "sd uhs DDR50"; + break; default: str = "invalid"; break; @@ -188,6 +207,15 @@ void mmc_add_host_debugfs(struct mmc_host *host) root, &host->clk_delay)) goto err_node; #endif +#ifdef CONFIG_FAIL_MMC_REQUEST + if (fail_request) + setup_fault_attr(&fail_default_attr, fail_request); + host->fail_mmc_request = fail_default_attr; + if (IS_ERR(fault_create_debugfs_attr("fail_mmc_request", + root, + &host->fail_mmc_request))) + goto err_node; +#endif return; err_node: diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 5700b1cbdfe..903c70b8ac4 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -239,7 +239,9 @@ static int mmc_get_ext_csd(struct mmc_card *card, u8 **new_ext_csd) */ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) { - int err = 0; + int err = 0, idx; + unsigned int part_size; + u8 hc_erase_grp_sz = 0, hc_wp_grp_sz = 0; BUG_ON(!card); @@ -337,10 +339,26 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) card->ext_csd.rel_sectors = ext_csd[EXT_CSD_REL_WR_SEC_C]; /* + * Note that the call to mmc_part_add defaults to read + * only. If this default assumption is changed, the call must + * take into account the value of boot_locked below. + */ + card->ext_csd.boot_locked = ext_csd[EXT_CSD_BOOT_WP] & + (EXT_CSD_BOOT_WP_B_PERM_WP_EN | + EXT_CSD_BOOT_WP_B_PWR_WP_EN); + + /* * There are two boot regions of equal size, defined in * multiples of 128K. */ - card->ext_csd.boot_size = ext_csd[EXT_CSD_BOOT_MULT] << 17; + if (ext_csd[EXT_CSD_BOOT_MULT] && mmc_boot_partition_access(card->host)) { + for (idx = 0; idx < MMC_NUM_BOOT_PARTITION; idx++) { + part_size = ext_csd[EXT_CSD_BOOT_MULT] << 17; + mmc_part_add(card, part_size, + EXT_CSD_PART_CONFIG_ACC_BOOT0 + idx, + "boot%d", idx, true, MMC_BLK_DATA_AREA_BOOT); + } + } } card->ext_csd.raw_hc_erase_gap_size = @@ -359,11 +377,12 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) * card has the Enhanced area enabled. If so, export enhanced * area offset and size to user by adding sysfs interface. */ + card->ext_csd.raw_partition_support = ext_csd[EXT_CSD_PARTITION_SUPPORT]; if ((ext_csd[EXT_CSD_PARTITION_SUPPORT] & 0x2) && (ext_csd[EXT_CSD_PARTITION_ATTRIBUTE] & 0x1)) { - u8 hc_erase_grp_sz = + hc_erase_grp_sz = ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; - u8 hc_wp_grp_sz = + hc_wp_grp_sz = ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; card->ext_csd.enhanced_area_en = 1; @@ -392,6 +411,42 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) card->ext_csd.enhanced_area_offset = -EINVAL; card->ext_csd.enhanced_area_size = -EINVAL; } + + /* + * General purpose partition feature support -- + * If ext_csd has the size of general purpose partitions, + * set size, part_cfg, partition name in mmc_part. + */ + if (ext_csd[EXT_CSD_PARTITION_SUPPORT] & + EXT_CSD_PART_SUPPORT_PART_EN) { + if (card->ext_csd.enhanced_area_en != 1) { + hc_erase_grp_sz = + ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; + hc_wp_grp_sz = + ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; + + card->ext_csd.enhanced_area_en = 1; + } + + for (idx = 0; idx < MMC_NUM_GP_PARTITION; idx++) { + if (!ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3] && + !ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3 + 1] && + !ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3 + 2]) + continue; + part_size = + (ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3 + 2] + << 16) + + (ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3 + 1] + << 8) + + ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3]; + part_size *= (size_t)(hc_erase_grp_sz * + hc_wp_grp_sz); + mmc_part_add(card, part_size << 19, + EXT_CSD_PART_CONFIG_ACC_GP0 + idx, + "gp%d", idx, false, + MMC_BLK_DATA_AREA_GP); + } + } card->ext_csd.sec_trim_mult = ext_csd[EXT_CSD_SEC_TRIM_MULT]; card->ext_csd.sec_erase_mult = @@ -402,14 +457,23 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) ext_csd[EXT_CSD_TRIM_MULT]; } - if (card->ext_csd.rev >= 5) + if (card->ext_csd.rev >= 5) { card->ext_csd.rel_param = ext_csd[EXT_CSD_WR_REL_PARAM]; + card->ext_csd.rst_n_function = ext_csd[EXT_CSD_RST_N_FUNCTION]; + } + card->ext_csd.raw_erased_mem_count = ext_csd[EXT_CSD_ERASED_MEM_CONT]; if (ext_csd[EXT_CSD_ERASED_MEM_CONT]) card->erased_byte = 0xFF; else card->erased_byte = 0x0; + if (card->ext_csd.rev >= 6) + card->ext_csd.generic_cmd6_time = 10 * + ext_csd[EXT_CSD_GENERIC_CMD6_TIME]; + else + card->ext_csd.generic_cmd6_time = 0; + out: return err; } @@ -530,6 +594,86 @@ static struct device_type mmc_type = { }; /* + * Select the PowerClass for the current bus width + * If power class is defined for 4/8 bit bus in the + * extended CSD register, select it by executing the + * mmc_switch command. + */ +static int mmc_select_powerclass(struct mmc_card *card, + unsigned int bus_width, u8 *ext_csd) +{ + int err = 0; + unsigned int pwrclass_val; + unsigned int index = 0; + struct mmc_host *host; + + BUG_ON(!card); + + host = card->host; + BUG_ON(!host); + + if (ext_csd == NULL) + return 0; + + /* Power class selection is supported for versions >= 4.0 */ + if (card->csd.mmca_vsn < CSD_SPEC_VER_4) + return 0; + + /* Power class values are defined only for 4/8 bit bus */ + if (bus_width == EXT_CSD_BUS_WIDTH_1) + return 0; + + switch (1 << host->ios.vdd) { + case MMC_VDD_165_195: + if (host->ios.clock <= 26000000) + index = EXT_CSD_PWR_CL_26_195; + else if (host->ios.clock <= 52000000) + index = (bus_width <= EXT_CSD_BUS_WIDTH_8) ? + EXT_CSD_PWR_CL_52_195 : + EXT_CSD_PWR_CL_DDR_52_195; + else if (host->ios.clock <= 200000000) + index = EXT_CSD_PWR_CL_200_195; + break; + case MMC_VDD_32_33: + case MMC_VDD_33_34: + case MMC_VDD_34_35: + case MMC_VDD_35_36: + if (host->ios.clock <= 26000000) + index = EXT_CSD_PWR_CL_26_360; + else if (host->ios.clock <= 52000000) + index = (bus_width <= EXT_CSD_BUS_WIDTH_8) ? + EXT_CSD_PWR_CL_52_360 : + EXT_CSD_PWR_CL_DDR_52_360; + else if (host->ios.clock <= 200000000) + index = EXT_CSD_PWR_CL_200_360; + break; + default: + pr_warning("%s: Voltage range not supported " + "for power class.\n", mmc_hostname(host)); + return -EINVAL; + } + + pwrclass_val = ext_csd[index]; + + if (bus_width & (EXT_CSD_BUS_WIDTH_8 | EXT_CSD_DDR_BUS_WIDTH_8)) + pwrclass_val = (pwrclass_val & EXT_CSD_PWR_CL_8BIT_MASK) >> + EXT_CSD_PWR_CL_8BIT_SHIFT; + else + pwrclass_val = (pwrclass_val & EXT_CSD_PWR_CL_4BIT_MASK) >> + EXT_CSD_PWR_CL_4BIT_SHIFT; + + /* If the power class is different from the default value */ + if (pwrclass_val > 0) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_POWER_CLASS, + pwrclass_val, + 0); + } + + return err; +} + +/* * Handle the detection and initialisation of a card. * * In the case of a resume, "oldcard" will contain the card @@ -548,11 +692,16 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, BUG_ON(!host); WARN_ON(!host->claimed); + /* Set correct bus mode for MMC before attempting init */ + if (!mmc_host_is_spi(host)) + mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN); + /* * Since we're changing the OCR value, we seem to * need to tell some cards to go back to the idle * state. We wait 1ms to give cards time to * respond. + * mmc_go_idle is needed for eMMC that are asleep */ mmc_go_idle(host); @@ -668,7 +817,8 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, */ if (card->ext_csd.enhanced_area_en) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_ERASE_GROUP_DEF, 1, 0); + EXT_CSD_ERASE_GROUP_DEF, 1, + card->ext_csd.generic_cmd6_time); if (err && err != -EBADMSG) goto free_card; @@ -711,7 +861,8 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, if ((card->ext_csd.hs_max_dtr != 0) && (host->caps & MMC_CAP_MMC_HIGHSPEED)) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_HS_TIMING, 1, 0); + EXT_CSD_HS_TIMING, 1, + card->ext_csd.generic_cmd6_time); if (err && err != -EBADMSG) goto free_card; @@ -780,10 +931,18 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, bus_width = bus_widths[idx]; if (bus_width == MMC_BUS_WIDTH_1) ddr = 0; /* no DDR for 1-bit width */ + err = mmc_select_powerclass(card, ext_csd_bits[idx][0], + ext_csd); + if (err) + pr_err("%s: power class selection to " + "bus width %d failed\n", + mmc_hostname(card->host), + 1 << bus_width); + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, ext_csd_bits[idx][0], - 0); + card->ext_csd.generic_cmd6_time); if (!err) { mmc_set_bus_width(card->host, bus_width); @@ -803,10 +962,18 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } if (!err && ddr) { + err = mmc_select_powerclass(card, ext_csd_bits[idx][1], + ext_csd); + if (err) + pr_err("%s: power class selection to " + "bus width %d ddr %d failed\n", + mmc_hostname(card->host), + 1 << bus_width, ddr); + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, ext_csd_bits[idx][1], - 0); + card->ext_csd.generic_cmd6_time); } if (err) { printk(KERN_WARNING "%s: switch to bus width %d ddr %d " @@ -891,6 +1058,7 @@ static void mmc_detect(struct mmc_host *host) mmc_claim_host(host); mmc_detach_bus(host); + mmc_power_off(host); mmc_release_host(host); } } @@ -900,16 +1068,20 @@ static void mmc_detect(struct mmc_host *host) */ static int mmc_suspend(struct mmc_host *host) { + int err = 0; + BUG_ON(!host); BUG_ON(!host->card); mmc_claim_host(host); - if (!mmc_host_is_spi(host)) + if (mmc_card_can_sleep(host)) + err = mmc_card_sleep(host); + else if (!mmc_host_is_spi(host)) mmc_deselect_cards(host); host->card->state &= ~MMC_STATE_HIGHSPEED; mmc_release_host(host); - return 0; + return err; } /* @@ -1016,6 +1188,10 @@ int mmc_attach_mmc(struct mmc_host *host) BUG_ON(!host); WARN_ON(!host->claimed); + /* Set correct bus mode for MMC before attempting attach */ + if (!mmc_host_is_spi(host)) + mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN); + err = mmc_send_op_cond(host, 0, &ocr); if (err) return err; diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 770c3d06f5d..7aa13d01a83 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -233,7 +233,7 @@ static int mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host, u32 opcode, void *buf, unsigned len) { - struct mmc_request mrq = {0}; + struct mmc_request mrq = {NULL}; struct mmc_command cmd = {0}; struct mmc_data data = {0}; struct scatterlist sg; @@ -454,7 +454,7 @@ static int mmc_send_bus_test(struct mmc_card *card, struct mmc_host *host, u8 opcode, u8 len) { - struct mmc_request mrq = {0}; + struct mmc_request mrq = {NULL}; struct mmc_command cmd = {0}; struct mmc_data data = {0}; struct scatterlist sg; diff --git a/drivers/mmc/core/quirks.c b/drivers/mmc/core/quirks.c index 3a596217029..6c3cf98a62e 100644 --- a/drivers/mmc/core/quirks.c +++ b/drivers/mmc/core/quirks.c @@ -21,6 +21,14 @@ #define SDIO_DEVICE_ID_TI_WL1271 0x4076 #endif +#ifndef SDIO_VENDOR_ID_STE +#define SDIO_VENDOR_ID_STE 0x0020 +#endif + +#ifndef SDIO_DEVICE_ID_STE_CW1200 +#define SDIO_DEVICE_ID_STE_CW1200 0x2280 +#endif + /* * This hook just adds a quirk for all sdio devices */ @@ -46,6 +54,9 @@ static const struct mmc_fixup mmc_fixup_methods[] = { SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, add_quirk, MMC_QUIRK_DISABLE_CD), + SDIO_FIXUP(SDIO_VENDOR_ID_STE, SDIO_DEVICE_ID_STE_CW1200, + add_quirk, MMC_QUIRK_BROKEN_BYTE_MODE_512), + END_FIXUP }; diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 0370e03e314..342b18c4afc 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -929,8 +929,6 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, err = mmc_send_relative_addr(host, &card->rca); if (err) return err; - - mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); } if (!oldcard) { @@ -1043,6 +1041,7 @@ static void mmc_sd_detect(struct mmc_host *host) mmc_claim_host(host); mmc_detach_bus(host); + mmc_power_off(host); mmc_release_host(host); } } diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c index 021fed15380..46a785419fa 100644 --- a/drivers/mmc/core/sd_ops.c +++ b/drivers/mmc/core/sd_ops.c @@ -67,7 +67,7 @@ EXPORT_SYMBOL_GPL(mmc_app_cmd); int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, struct mmc_command *cmd, int retries) { - struct mmc_request mrq = {0}; + struct mmc_request mrq = {NULL}; int i, err; @@ -244,7 +244,7 @@ int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca) int mmc_app_send_scr(struct mmc_card *card, u32 *scr) { int err; - struct mmc_request mrq = {0}; + struct mmc_request mrq = {NULL}; struct mmc_command cmd = {0}; struct mmc_data data = {0}; struct scatterlist sg; @@ -303,7 +303,7 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) int mmc_sd_switch(struct mmc_card *card, int mode, int group, u8 value, u8 *resp) { - struct mmc_request mrq = {0}; + struct mmc_request mrq = {NULL}; struct mmc_command cmd = {0}; struct mmc_data data = {0}; struct scatterlist sg; @@ -348,7 +348,7 @@ int mmc_sd_switch(struct mmc_card *card, int mode, int group, int mmc_app_sd_status(struct mmc_card *card, void *ssr) { int err; - struct mmc_request mrq = {0}; + struct mmc_request mrq = {NULL}; struct mmc_command cmd = {0}; struct mmc_data data = {0}; struct scatterlist sg; diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 262fff01917..698d813cff3 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -408,8 +408,6 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr, */ if (oldcard) oldcard->rca = card->rca; - - mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); } /* @@ -597,6 +595,7 @@ out: mmc_claim_host(host); mmc_detach_bus(host); + mmc_power_off(host); mmc_release_host(host); } } diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c index f087d876c57..b0517cc0620 100644 --- a/drivers/mmc/core/sdio_ops.c +++ b/drivers/mmc/core/sdio_ops.c @@ -121,7 +121,7 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz) { - struct mmc_request mrq = {0}; + struct mmc_request mrq = {NULL}; struct mmc_command cmd = {0}; struct mmc_data data = {0}; struct scatterlist sg; @@ -144,8 +144,11 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, cmd.arg |= fn << 28; cmd.arg |= incr_addr ? 0x04000000 : 0x00000000; cmd.arg |= addr << 9; - if (blocks == 1 && blksz <= 512) - cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */ + if (blocks == 1 && blksz < 512) + cmd.arg |= blksz; /* byte mode */ + else if (blocks == 1 && blksz == 512 && + !(mmc_card_broken_byte_mode_512(card))) + cmd.arg |= 0; /* byte mode, 0==512 */ else cmd.arg |= 0x08000000 | blocks; /* block mode */ cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC; diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 56e9a416826..8d901ba1e54 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -19,6 +19,7 @@ #include <linux/err.h> #include <linux/highmem.h> #include <linux/log2.h> +#include <linux/pm_runtime.h> #include <linux/mmc/host.h> #include <linux/mmc/card.h> #include <linux/amba/bus.h> @@ -52,6 +53,9 @@ static unsigned int fmax = 515633; * @sdio: variant supports SDIO * @st_clkdiv: true if using a ST-specific clock divider algorithm * @blksz_datactrl16: true if Block size is at b16..b30 position in datactrl register + * @non_power_of_2_blksize: true if block sizes can be other than power of two + * @pwrreg_powerup: power up value for MMCIPOWER register + * @signal_direction: input/out direction of bus signals can be indicated */ struct variant_data { unsigned int clkreg; @@ -62,18 +66,23 @@ struct variant_data { bool sdio; bool st_clkdiv; bool blksz_datactrl16; + bool non_power_of_2_blksize; + unsigned int pwrreg_powerup; + bool signal_direction; }; static struct variant_data variant_arm = { .fifosize = 16 * 4, .fifohalfsize = 8 * 4, .datalength_bits = 16, + .pwrreg_powerup = MCI_PWR_UP, }; static struct variant_data variant_arm_extended_fifo = { .fifosize = 128 * 4, .fifohalfsize = 64 * 4, .datalength_bits = 16, + .pwrreg_powerup = MCI_PWR_UP, }; static struct variant_data variant_u300 = { @@ -82,6 +91,8 @@ static struct variant_data variant_u300 = { .clkreg_enable = MCI_ST_U300_HWFCEN, .datalength_bits = 16, .sdio = true, + .pwrreg_powerup = MCI_PWR_ON, + .signal_direction = true, }; static struct variant_data variant_ux500 = { @@ -92,6 +103,8 @@ static struct variant_data variant_ux500 = { .datalength_bits = 24, .sdio = true, .st_clkdiv = true, + .pwrreg_powerup = MCI_PWR_ON, + .signal_direction = true, }; static struct variant_data variant_ux500v2 = { @@ -103,6 +116,9 @@ static struct variant_data variant_ux500v2 = { .sdio = true, .st_clkdiv = true, .blksz_datactrl16 = true, + .non_power_of_2_blksize = true, + .pwrreg_powerup = MCI_PWR_ON, + .signal_direction = true, }; /* @@ -165,13 +181,7 @@ mmci_request_end(struct mmci_host *host, struct mmc_request *mrq) host->mrq = NULL; host->cmd = NULL; - /* - * Need to drop the host lock here; mmc_request_done may call - * back into the driver... - */ - spin_unlock(&host->lock); mmc_request_done(host->mmc, mrq); - spin_lock(&host->lock); } static void mmci_set_mask1(struct mmci_host *host, unsigned int mask) @@ -529,7 +539,7 @@ static void mmci_post_request(struct mmc_host *mmc, struct mmc_request *mrq, if (chan) { if (err) dmaengine_terminate_all(chan); - if (err || data->host_cookie) + if (data->host_cookie) dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, dir); mrq->data->host_cookie = 0; @@ -592,7 +602,6 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) writel(host->size, base + MMCIDATALENGTH); blksz_bits = ffs(data->blksz) - 1; - BUG_ON(1 << blksz_bits != data->blksz); if (variant->blksz_datactrl16) datactrl = MCI_DPSM_ENABLE | (data->blksz << 16); @@ -602,6 +611,34 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) if (data->flags & MMC_DATA_READ) datactrl |= MCI_DPSM_DIRECTION; + /* The ST Micro variants has a special bit to enable SDIO */ + if (variant->sdio && host->mmc->card) + if (mmc_card_sdio(host->mmc->card)) { + /* + * The ST Micro variants has a special bit + * to enable SDIO. + */ + datactrl |= MCI_ST_DPSM_SDIOEN; + + /* + * The ST Micro variant for SDIO transfer sizes + * less then or equal to 8 bytes needs to have clock + * H/W flow control disabled. Since flow control is + * not really needed for anything that fits in the + * FIFO, we can disable it for any write smaller + * than the FIFO size. + */ + if ((host->size <= variant->fifosize) && + (data->flags & MMC_DATA_WRITE)) + writel(readl(host->base + MMCICLOCK) & + ~variant->clkreg_enable, + host->base + MMCICLOCK); + else + writel(readl(host->base + MMCICLOCK) | + variant->clkreg_enable, + host->base + MMCICLOCK); + } + /* * Attempt to use DMA operation mode, if this * should fail, fall back to PIO mode @@ -630,11 +667,6 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) irqmask = MCI_TXFIFOHALFEMPTYMASK; } - /* The ST Micro variants has a special bit to enable SDIO */ - if (variant->sdio && host->mmc->card) - if (mmc_card_sdio(host->mmc->card)) - datactrl |= MCI_ST_DPSM_SDIOEN; - writel(datactrl, base + MMCIDATACTRL); writel(readl(base + MMCIMASK0) & ~MCI_DATAENDMASK, base + MMCIMASK0); mmci_set_mask1(host, irqmask); @@ -776,7 +808,24 @@ static int mmci_pio_read(struct mmci_host *host, char *buffer, unsigned int rema if (count <= 0) break; - readsl(base + MMCIFIFO, ptr, count >> 2); + /* + * SDIO especially may want to receive something that is + * not divisible by 4 (as opposed to card sectors + * etc). Therefore make sure we always read the last bytes + * out of the FIFO. + */ + switch (count) { + case 1: + case 3: + readsb(base + MMCIFIFO, ptr, count); + break; + case 2: + readsw(base + MMCIFIFO, ptr, 1); + break; + default: + readsl(base + MMCIFIFO, ptr, count >> 2); + break; + } ptr += count; remain -= count; @@ -805,23 +854,6 @@ static int mmci_pio_write(struct mmci_host *host, char *buffer, unsigned int rem count = min(remain, maxcnt); /* - * The ST Micro variant for SDIO transfer sizes - * less then 8 bytes should have clock H/W flow - * control disabled. - */ - if (variant->sdio && - mmc_card_sdio(host->mmc->card)) { - if (count < 8) - writel(readl(host->base + MMCICLOCK) & - ~variant->clkreg_enable, - host->base + MMCICLOCK); - else - writel(readl(host->base + MMCICLOCK) | - variant->clkreg_enable, - host->base + MMCICLOCK); - } - - /* * SDIO especially may want to send something that is * not divisible by 4 (as opposed to card sectors * etc), and the FIFO only accept full 32-bit writes. @@ -972,11 +1004,14 @@ static irqreturn_t mmci_irq(int irq, void *dev_id) static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct mmci_host *host = mmc_priv(mmc); + struct variant_data *variant = host->variant; unsigned long flags; WARN_ON(host->mrq != NULL); - if (mrq->data && !is_power_of_2(mrq->data->blksz)) { + if (mrq->data && + !variant->non_power_of_2_blksize && + !is_power_of_2(mrq->data->blksz)) { dev_err(mmc_dev(mmc), "unsupported block size (%d bytes)\n", mrq->data->blksz); mrq->cmd->error = -EINVAL; @@ -1002,10 +1037,15 @@ static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq) static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct mmci_host *host = mmc_priv(mmc); + struct variant_data *variant = host->variant; u32 pwr = 0; unsigned long flags; int ret; + if (host->plat->ios_handler && + host->plat->ios_handler(mmc_dev(mmc), ios)) + dev_err(mmc_dev(mmc), "platform ios_handler failed\n"); + switch (ios->power_mode) { case MMC_POWER_OFF: if (host->vcc) @@ -1025,19 +1065,35 @@ static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) return; } } - if (host->plat->vdd_handler) - pwr |= host->plat->vdd_handler(mmc_dev(mmc), ios->vdd, - ios->power_mode); - /* The ST version does not have this, fall through to POWER_ON */ - if (host->hw_designer != AMBA_VENDOR_ST) { - pwr |= MCI_PWR_UP; - break; - } + /* + * The ST Micro variant doesn't have the PL180s MCI_PWR_UP + * and instead uses MCI_PWR_ON so apply whatever value is + * configured in the variant data. + */ + pwr |= variant->pwrreg_powerup; + + break; case MMC_POWER_ON: pwr |= MCI_PWR_ON; break; } + if (variant->signal_direction && ios->power_mode != MMC_POWER_OFF) { + /* + * The ST Micro variant has some additional bits + * indicating signal direction for the signals in + * the SD/MMC bus and feedback-clock usage. + */ + pwr |= host->plat->sigdir; + + if (ios->bus_width == MMC_BUS_WIDTH_4) + pwr &= ~MCI_ST_DATA74DIREN; + else if (ios->bus_width == MMC_BUS_WIDTH_1) + pwr &= (~MCI_ST_DATA74DIREN & + ~MCI_ST_DATA31DIREN & + ~MCI_ST_DATA2DIREN); + } + if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) { if (host->hw_designer != AMBA_VENDOR_ST) pwr |= MCI_ROD; @@ -1320,6 +1376,10 @@ static int __devinit mmci_probe(struct amba_device *dev, amba_set_drvdata(dev, mmc); + pm_runtime_enable(mmc->parent); + if (pm_runtime_get_sync(mmc->parent) < 0) + dev_err(mmc_dev(mmc), "failed pm_runtime_get_sync\n"); + dev_info(&dev->dev, "%s: PL%03x manf %x rev%u at 0x%08llx irq %d,%d (pio)\n", mmc_hostname(mmc), amba_part(dev), amba_manf(dev), amba_rev(dev), (unsigned long long)dev->res.start, @@ -1412,6 +1472,9 @@ static int mmci_suspend(struct amba_device *dev, pm_message_t state) ret = mmc_suspend_host(mmc); if (ret == 0) writel(0, host->base + MMCIMASK0); + + if (pm_runtime_put_sync(mmc->parent) < 0) + dev_err(mmc_dev(mmc), "failed pm_runtime_put_sync\n"); } return ret; @@ -1425,6 +1488,9 @@ static int mmci_resume(struct amba_device *dev) if (mmc) { struct mmci_host *host = mmc_priv(mmc); + if (pm_runtime_get_sync(mmc->parent) < 0) + dev_err(mmc_dev(mmc), "failed pm_runtime_get_sync\n"); + writel(MCI_IRQENABLE, host->base + MMCIMASK0); ret = mmc_resume_host(mmc); diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h index 79e4143ab9d..89eb2e3556d 100644 --- a/drivers/mmc/host/mmci.h +++ b/drivers/mmc/host/mmci.h @@ -13,16 +13,6 @@ #define MCI_PWR_ON 0x03 #define MCI_OD (1 << 6) #define MCI_ROD (1 << 7) -/* - * The ST Micro version does not have ROD and reuse the voltage registers - * for direction settings - */ -#define MCI_ST_DATA2DIREN (1 << 2) -#define MCI_ST_CMDDIREN (1 << 3) -#define MCI_ST_DATA0DIREN (1 << 4) -#define MCI_ST_DATA31DIREN (1 << 5) -#define MCI_ST_FBCLKEN (1 << 7) -#define MCI_ST_DATA74DIREN (1 << 8) #define MMCICLOCK 0x004 #define MCI_CLK_ENABLE (1 << 8) @@ -160,7 +150,7 @@ (MCI_RXFIFOHALFFULLMASK | MCI_RXDATAAVLBLMASK | \ MCI_TXFIFOHALFEMPTYMASK) -#define NR_SG 16 +#define NR_SG 128 struct clk; struct variant_data; diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 26c528648f3..d7cebb30b6d 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -98,6 +98,9 @@ static int ricoh_mmc_probe_slot(struct sdhci_pci_slot *slot) SDHCI_TIMEOUT_CLK_UNIT | SDHCI_CAN_VDD_330 | SDHCI_CAN_DO_SDMA; + + slot->host->mmc->caps2 = MMC_CAP2_BOOTPART_NOACC; + return 0; } diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c index c90ddb61cc5..3aa0cccc9fa 100644 --- a/drivers/net/smsc911x.c +++ b/drivers/net/smsc911x.c @@ -54,6 +54,8 @@ #include <linux/phy.h> #include <linux/smsc911x.h> #include <linux/device.h> +#include <linux/regulator/consumer.h> + #include "smsc911x.h" #define SMSC_CHIPNAME "smsc911x" @@ -134,6 +136,10 @@ struct smsc911x_data { /* register access functions */ const struct smsc911x_ops *ops; + + /* regulators */ + struct regulator *regulator_vddvario; + struct regulator *regulator_vdd33a; }; /* Easy access to information */ @@ -358,6 +364,81 @@ out: spin_unlock_irqrestore(&pdata->dev_lock, flags); } +/* Enable resources(clocks and regulators) */ +static int smsc911x_enable_resources(struct platform_device *pdev, bool enable) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct smsc911x_data *pdata = netdev_priv(ndev); + int err = 0; + + /* enable/diable regulator for vddvario */ + if (pdata->regulator_vddvario) { + if (enable) { + err = regulator_enable(pdata->regulator_vddvario); + if (err < 0) { + netdev_err(ndev, "%s: regulator_enable failed '%s'\n", + __func__, "vddvario"); + } + } else + err = regulator_disable(pdata->regulator_vdd33a); + } + + /* enable/diableregulator for vdd33a */ + if (pdata->regulator_vdd33a) { + if (enable) { + err = regulator_enable(pdata->regulator_vdd33a); + if (err < 0) { + netdev_err(ndev, "%s: regulator_enable failed '%s'\n", + __func__, "vdd33a"); + } + } else + err = regulator_disable(pdata->regulator_vdd33a); + } + return err; +} + + +/* Request resources(clocks and regulators) */ +static int smsc911x_request_resources(struct platform_device *pdev, + bool request) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct smsc911x_data *pdata = netdev_priv(ndev); + int err = 0; + + /* Request regulator for vddvario */ + if (request && !pdata->regulator_vddvario) { + pdata->regulator_vddvario = regulator_get(&pdev->dev, + "vddvario"); + if (IS_ERR(pdata->regulator_vddvario)) { + netdev_warn(ndev, + "%s: Failed to get regulator '%s'\n", + __func__, "vddvario"); + pdata->regulator_vddvario = NULL; + } + } else if (!request && pdata->regulator_vddvario) { + regulator_put(pdata->regulator_vddvario); + pdata->regulator_vddvario = NULL; + } + + /* Request regulator for vdd33a */ + if (request && !pdata->regulator_vddvario) { + pdata->regulator_vdd33a = regulator_get(&pdev->dev, + "vdd33a"); + if (IS_ERR(pdata->regulator_vdd33a)) { + netdev_warn(ndev, + "%s: Failed to get regulator '%s'\n", + __func__, "vdd33a"); + pdata->regulator_vdd33a = NULL; + } + } else if (!request && pdata->regulator_vdd33a) { + regulator_put(pdata->regulator_vdd33a); + pdata->regulator_vdd33a = NULL; + } + + return err; +} + /* waits for MAC not busy, with timeout. Only called by smsc911x_mac_read * and smsc911x_mac_write, so assumes mac_lock is held */ static int smsc911x_mac_complete(struct smsc911x_data *pdata) @@ -2049,6 +2130,7 @@ static int __devexit smsc911x_drv_remove(struct platform_device *pdev) struct net_device *dev; struct smsc911x_data *pdata; struct resource *res; + int retval; dev = platform_get_drvdata(pdev); BUG_ON(!dev); @@ -2076,6 +2158,12 @@ static int __devexit smsc911x_drv_remove(struct platform_device *pdev) iounmap(pdata->ioaddr); + if (smsc911x_enable_resources(pdev, false)) + pr_warn("Could not disable resource\n"); + + retval = smsc911x_request_resources(pdev, false); + /* ignore not all have regulators */ + free_netdev(dev); return 0; @@ -2106,6 +2194,7 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev) unsigned int intcfg = 0; int res_size, irq_flags; int retval; + int to = 100; pr_info("Driver version %s\n", SMSC_DRV_VERSION); @@ -2160,6 +2249,17 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev) pdata->dev = dev; pdata->msg_enable = ((1 << debug) - 1); + platform_set_drvdata(pdev, dev); + + retval = smsc911x_request_resources(pdev, true); + /* ignore not all have regulators */ + + retval = smsc911x_enable_resources(pdev, true); + if (retval) { + pr_warn("Could not enable resource\n"); + goto out_0; + } + if (pdata->ioaddr == NULL) { SMSC_WARN(pdata, probe, "Error smsc911x base address invalid"); retval = -ENOMEM; @@ -2172,6 +2272,18 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev) if (config->shift) pdata->ops = &shifted_smsc911x_ops; + /* poll the READY bit in PMT_CTRL. Any other access to the device is + * forbidden while this bit isn't set. Try for 100ms + */ + while (!(smsc911x_reg_read(pdata, PMT_CTRL) & PMT_CTRL_READY_) && --to) + udelay(1000); + + if (to == 0) { + pr_err("Device not READY in 100ms aborting\n"); + goto out_0; + } + + retval = smsc911x_init(dev); if (retval < 0) goto out_unmap_io_3; @@ -2264,6 +2376,7 @@ out_0: return retval; } + #ifdef CONFIG_PM /* This implementation assumes the devices remains powered on its VDDVARIO * pins during suspend. */ diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 57de051a74b..6df45ff76ba 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -235,6 +235,32 @@ config CHARGER_GPIO This driver can be build as a module. If so, the module will be called gpio-charger. +config AB8500_BM + bool "AB8500 Battery Management Driver" + depends on AB8500_CORE && AB8500_GPADC && ARCH_U8500 + help + Say Y to include support for AB8500 battery management. + +config AB8500_BATTERY_THERM_ON_BATCTRL + bool "Thermistor connected on BATCTRL ADC" + depends on AB8500_BM + help + Say Y to enable battery temperature measurements using + thermistor connected on BATCTRL ADC. + +config AB5500_BM + bool "AB5500 Battery Management Driver" + depends on AB5500_CORE && AB5500_GPADC && MACH_U5500 + help + Say Y to include support for AB5500 battery management. + +config AB5500_BATTERY_THERM_ON_BATCTRL + bool "Thermistor connected on BATCTRL ADC" + depends on AB5500_BM + help + Say Y to enable battery temperature measurements using + thermistor connected on BATCTRL ADC. + config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" depends on MFD_MAX8997 && REGULATOR_MAX8997 diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b4af13dd8b6..6585c0aee99 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -36,5 +36,7 @@ obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o +obj-$(CONFIG_AB8500_BM) += ab8500_charger.o ab8500_btemp.o ab8500_fg.o ab8500_chargalg.o +obj-$(CONFIG_AB5500_BM) += ab5500_charger.o abx500_chargalg.o ab5500_fg.o ab5500_btemp.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o diff --git a/drivers/power/ab5500_btemp.c b/drivers/power/ab5500_btemp.c new file mode 100644 index 00000000000..7867455f493 --- /dev/null +++ b/drivers/power/ab5500_btemp.c @@ -0,0 +1,889 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Battery temperature driver for ab5500 + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/mfd/abx500/ab5500-bm.h> +#include <linux/mfd/abx500/ab5500-gpadc.h> + +#define BTEMP_THERMAL_LOW_LIMIT -10 +#define BTEMP_THERMAL_MED_LIMIT 0 +#define BTEMP_THERMAL_HIGH_LIMIT_62 62 + +#define BTEMP_BATCTRL_CURR_SRC_7UA 7 +#define BTEMP_BATCTRL_CURR_SRC_15UA 15 +#define BTEMP_BATCTRL_CURR_SRC_20UA 20 + +#define UART_MODE 0x0F +#define BAT_CUR_SRC 0x1F +#define RESIS_ID_MODE 0x03 +#define RESET 0x00 +#define ADOUT_10K_PULL_UP 0x07 + +#define to_ab5500_btemp_device_info(x) container_of((x), \ + struct ab5500_btemp, btemp_psy); + +/** + * struct ab5500_btemp_interrupts - ab5500 interrupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab5500_btemp_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab5500_btemp_events { + bool batt_rem; + bool usb_conn; +}; + +/** + * struct ab5500_btemp - ab5500 BTEMP device information + * @dev: Pointer to the structure device + * @chip_id: Chip-Id of the AB5500 + * @curr_source: What current source we use, in uA + * @bat_temp: Battery temperature in degree Celcius + * @prev_bat_temp Last dispatched battery temperature + * @node: struct of type list_head + * @parent: Pointer to the struct ab5500 + * @gpadc: Pointer to the struct gpadc + * @gpadc-auto: Pointer to the struct adc_auto_input + * @pdata: Pointer to the ab5500_btemp platform data + * @bat: Pointer to the ab5500_bm platform data + * @btemp_psy: Structure for BTEMP specific battery properties + * @events: Structure for information about events triggered + * @btemp_wq: Work queue for measuring the temperature periodically + * @btemp_periodic_work: Work for measuring the temperature periodically + */ +struct ab5500_btemp { + struct device *dev; + u8 chip_id; + int curr_source; + int bat_temp; + int prev_bat_temp; + struct list_head node; + struct ab5500 *parent; + struct ab5500_gpadc *gpadc; + struct adc_auto_input *gpadc_auto; + struct abx500_btemp_platform_data *pdata; + struct abx500_bm_data *bat; + struct power_supply btemp_psy; + struct ab5500_btemp_events events; + struct workqueue_struct *btemp_wq; + struct delayed_work btemp_periodic_work; +}; + +/* BTEMP power supply properties */ +static enum power_supply_property ab5500_btemp_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_TEMP, +}; + +static LIST_HEAD(ab5500_btemp_list); + +struct ab5500_btemp *ab5500_btemp_get(void) +{ + struct ab5500_btemp *di; + di = list_first_entry(&ab5500_btemp_list, struct ab5500_btemp, node); + + return di; +} + +/** + * ab5500_btemp_get_batctrl_temp() - get the temperature + * @di: pointer to the ab5500_btemp structure + * + * Returns the batctrl temperature in millidegrees + */ +int ab5500_btemp_get_batctrl_temp(struct ab5500_btemp *di) +{ + return di->bat_temp * 1000; +} + +/** + * ab5500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance + * @di: pointer to the ab5500_btemp structure + * @v_batctrl: measured batctrl voltage + * + * This function returns the battery resistance that is + * derived from the BATCTRL voltage. + * Returns value in Ohms. + */ +static int ab5500_btemp_batctrl_volt_to_res(struct ab5500_btemp *di, + int v_batctrl) +{ + int rbs; + + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL) { + /* + * If the battery has internal NTC, we use the current + * source to calculate the resistance, 7uA or 20uA + */ + rbs = v_batctrl * 1000 / di->curr_source; + } else { + /* + * BAT_CTRL is internally + * connected to 1.8V through a 10k resistor + */ + rbs = (10000 * (v_batctrl)) / (1800 - v_batctrl); + } + return rbs; +} + +/** + * ab5500_btemp_read_batctrl_voltage() - measure batctrl voltage + * @di: pointer to the ab5500_btemp structure + * + * This function returns the voltage on BATCTRL. Returns value in mV. + */ +static int ab5500_btemp_read_batctrl_voltage(struct ab5500_btemp *di) +{ + int vbtemp; + static int prev; + + vbtemp = ab5500_gpadc_convert(di->gpadc, BAT_CTRL); + if (vbtemp < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value", + __func__); + return prev; + } + prev = vbtemp; + return vbtemp; +} + +/** + * ab5500_btemp_curr_source_enable() - enable/disable batctrl current source + * @di: pointer to the ab5500_btemp structure + * @enable: enable or disable the current source + * + * Enable or disable the current sources for the BatCtrl AD channel + */ +static int ab5500_btemp_curr_source_enable(struct ab5500_btemp *di, + bool enable) +{ + int ret = 0; + + /* Only do this for batteries with internal NTC */ + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) { + + dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_UART, + UART_MODE, RESIS_ID_MODE); + if (ret) { + dev_err(di->dev, + "%s failed setting resistance identification mode\n", + __func__); + return ret; + } + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_URI, + BAT_CUR_SRC, BAT_CTRL_15U_ENA); + if (ret) { + dev_err(di->dev, "%s failed enabling current source\n", + __func__); + goto disable_curr_source; + } + } else if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) { + dev_dbg(di->dev, "Disable BATCTRL curr source\n"); + + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_URI, + BAT_CUR_SRC, RESET); + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + goto disable_curr_source; + } + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_UART, + UART_MODE, RESET); + if (ret) { + dev_err(di->dev, "%s failed disabling force comp\n", + __func__); + } + } + return ret; +disable_curr_source: + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_URI, + BAT_CUR_SRC, RESET); + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + } + return ret; +} + +/** + * ab5500_btemp_get_batctrl_res() - get battery resistance + * @di: pointer to the ab5500_btemp structure + * + * This function returns the battery pack identification resistance. + * Returns value in Ohms. + */ +static int ab5500_btemp_get_batctrl_res(struct ab5500_btemp *di) +{ + int ret; + int batctrl; + int res; + + ret = ab5500_btemp_curr_source_enable(di, true); + /* TODO: This delay has to be optimised */ + mdelay(1000); + if (ret) { + dev_err(di->dev, "%s curr source enable failed\n", __func__); + return ret; + } + + batctrl = ab5500_btemp_read_batctrl_voltage(di); + res = ab5500_btemp_batctrl_volt_to_res(di, batctrl); + + ret = ab5500_btemp_curr_source_enable(di, false); + if (ret) { + dev_err(di->dev, "%s curr source disable failed\n", __func__); + return ret; + } + + dev_dbg(di->dev, "%s batctrl: %d res: %d ", + __func__, batctrl, res); + + return res; +} + +/** + * ab5500_btemp_res_to_temp() - resistance to temperature + * @di: pointer to the ab5500_btemp structure + * @tbl: pointer to the resiatance to temperature table + * @tbl_size: size of the resistance to temperature table + * @res: resistance to calculate the temperature from + * + * This function returns the battery temperature in degrees Celcius + * based on the NTC resistance. + */ +static int ab5500_btemp_res_to_temp(struct ab5500_btemp *di, + const struct abx500_res_to_temp *tbl, int tbl_size, int res) +{ + int i, temp; + /* + * Calculate the formula for the straight line + * Simple interpolation if we are within + * the resistance table limits, extrapolate + * if resistance is outside the limits. + */ + if (res > tbl[0].resist) + i = 0; + else if (res <= tbl[tbl_size - 1].resist) + i = tbl_size - 2; + else { + i = 0; + while (!(res <= tbl[i].resist && + res > tbl[i + 1].resist)) + i++; + } + + temp = tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) * + (res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); + return temp; +} + +/** + * ab5500_btemp_measure_temp() - measure battery temperature + * @di: pointer to the ab5500_btemp structure + * + * Returns battery temperature (on success) else the previous temperature + */ +static int ab5500_btemp_measure_temp(struct ab5500_btemp *di) +{ + int temp, ret; + static int prev; + int rbat, vntc; + int rntc = 0; + u8 id; + + id = di->bat->batt_id; + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && + id != BATTERY_UNKNOWN) { + rbat = ab5500_btemp_get_batctrl_res(di); + if (rbat < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", + __func__); + /* + * Return out-of-range temperature so that + * charging is stopped + */ + return BTEMP_THERMAL_LOW_LIMIT; + } + + temp = ab5500_btemp_res_to_temp(di, + di->bat->bat_type[id].r_to_t_tbl, + di->bat->bat_type[id].n_temp_tbl_elements, rbat); + } else { + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_UART, + UART_MODE, ADOUT_10K_PULL_UP); + if (ret) { + dev_err(di->dev, + "failed to enable 10k pull up to Vadout\n"); + } + vntc = ab5500_gpadc_convert(di->gpadc, BTEMP_BALL); + if (vntc < 0) { + dev_err(di->dev, + "%s gpadc conversion failed," + " using previous value\n", __func__); + return prev; + } + /* + * The PCB NTC is sourced from 2.75v via a 10kOhm + * resistor. + */ + rntc = 10000 * vntc / (27500 - vntc); + + temp = ab5500_btemp_res_to_temp(di, + di->bat->bat_type[id].r_to_t_tbl, + di->bat->bat_type[id].n_temp_tbl_elements, rntc); + prev = temp; + } + dev_dbg(di->dev, "Battery temperature is %d\n", temp); + return temp; +} + +/** + * ab5500_btemp_id() - Identify the connected battery + * @di: pointer to the ab5500_btemp structure + * + * This function will try to identify the battery by reading the ID + * resistor. Some brands use a combined ID resistor with a NTC resistor to + * both be able to identify and to read the temperature of it. + */ +static int ab5500_btemp_id(struct ab5500_btemp *di) +{ + int res; + u8 i; + + di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA; + di->bat->batt_id = BATTERY_UNKNOWN; + + res = ab5500_btemp_get_batctrl_res(di); + if (res < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", __func__); + return -ENXIO; + } + + /* BATTERY_UNKNOWN is defined on position 0, skip it! */ + for (i = BATTERY_UNKNOWN + 1; i < di->bat->n_btypes; i++) { + if ((res <= di->bat->bat_type[i].resis_high) && + (res >= di->bat->bat_type[i].resis_low)) { + dev_dbg(di->dev, "Battery detected on %s" + " low %d < res %d < high: %d" + " index: %d\n", + di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL ? + "BATCTRL" : "BATTEMP", + di->bat->bat_type[i].resis_low, res, + di->bat->bat_type[i].resis_high, i); + + di->bat->batt_id = i; + break; + } + } + + if (di->bat->batt_id == BATTERY_UNKNOWN) { + dev_warn(di->dev, "Battery identified as unknown" + ", resistance %d Ohm\n", res); + return -ENXIO; + } + + /* + * We only have to change current source if the + * detected type is Type 1, else we use the 7uA source + */ + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && + di->bat->batt_id == 1) { + dev_dbg(di->dev, "Set BATCTRL current source to 15uA\n"); + di->curr_source = BTEMP_BATCTRL_CURR_SRC_15UA; + } + + return di->bat->batt_id; +} + +/** + * ab5500_btemp_periodic_work() - Measuring the temperature periodically + * @work: pointer to the work_struct structure + * + * Work function for measuring the temperature periodically + */ +static void ab5500_btemp_periodic_work(struct work_struct *work) +{ + struct ab5500_btemp *di = container_of(work, + struct ab5500_btemp, btemp_periodic_work.work); + + di->bat_temp = ab5500_btemp_measure_temp(di); + + if (di->bat_temp != di->prev_bat_temp) { + di->prev_bat_temp = di->bat_temp; + power_supply_changed(&di->btemp_psy); + } + di->bat->temp_now = di->bat_temp; + + /* Schedule a new measurement */ + queue_delayed_work(di->btemp_wq, + &di->btemp_periodic_work, + round_jiffies(20 * HZ)); +} + +/** + * ab5500_btemp_batt_removal_handler() - battery removal detected + * @irq: interrupt number + * @_di: void pointer that has to address of ab5500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_btemp_batt_removal_handler(int irq, void *_di) +{ + struct ab5500_btemp *di = _di; + dev_err(di->dev, "Battery removal detected!\n"); + + di->events.batt_rem = true; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab5500_btemp_batt_attach_handler() - battery insertion detected + * @irq: interrupt number + * @_di: void pointer that has to address of ab5500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_btemp_batt_attach_handler(int irq, void *_di) +{ + struct ab5500_btemp *di = _di; + dev_err(di->dev, "Battery attached!\n"); + + di->events.batt_rem = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab5500_btemp_periodic() - Periodic temperature measurements + * @di: pointer to the ab5500_btemp structure + * @enable: enable or disable periodic temperature measurements + * + * Starts of stops periodic temperature measurements. Periodic measurements + * should only be done when a charger is connected. + */ +static void ab5500_btemp_periodic(struct ab5500_btemp *di, + bool enable) +{ + dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n", + enable); + + if (enable) + queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0); + else + cancel_delayed_work_sync(&di->btemp_periodic_work); +} + +/** + * ab5500_btemp_get_property() - get the btemp properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the btemp + * properties by reading the sysfs files. + * online: presence of the battery + * present: presence of the battery + * technology: battery technology + * temp: battery temperature + * Returns error code in case of failure else 0(on success) + */ +static int ab5500_btemp_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab5500_btemp *di; + + di = to_ab5500_btemp_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + if (di->events.batt_rem) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = di->bat->bat_type[di->bat->batt_id].name; + break; + case POWER_SUPPLY_PROP_TEMP: + if (di->bat->batt_id == BATTERY_UNKNOWN) + /* + * In case the battery is not identified, its assumed that + * we are using the power supply and since no monitoring is + * done for the same, a nominal temp is hardocded. + */ + val->intval = 250; + else + val->intval = di->bat_temp * 10; + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab5500_btemp_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab5500_btemp *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab5500_btemp_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->type) { + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && di->events.usb_conn) { + di->events.usb_conn = false; + ab5500_btemp_periodic(di, + false); + } + /* USB connected */ + else if (ret.intval && !di->events.usb_conn) { + di->events.usb_conn = true; + ab5500_btemp_periodic(di, true); + } + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab5500_btemp_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is pointing to the function pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in the external power + * supply to the btemp. + */ +static void ab5500_btemp_external_power_changed(struct power_supply *psy) +{ + struct ab5500_btemp *di = to_ab5500_btemp_device_info(psy); + + class_for_each_device(power_supply_class, NULL, + &di->btemp_psy, ab5500_btemp_get_ext_psy_data); +} + +/* ab5500 btemp driver interrupts and their respective isr */ +static struct ab5500_btemp_interrupts ab5500_btemp_irq[] = { + {"BATT_REMOVAL", ab5500_btemp_batt_removal_handler}, + {"BATT_ATTACH", ab5500_btemp_batt_attach_handler}, +}; +static int ab5500_btemp_bat_temp_trig(int mux) +{ + struct ab5500_btemp *di = ab5500_btemp_get(); + + if (di->bat_temp < BTEMP_THERMAL_LOW_LIMIT) { + dev_err(di->dev, + "battery temp less than lower threshold (-10 deg cel)\n"); + power_supply_changed(&di->btemp_psy); + } else if (di->bat_temp > BTEMP_THERMAL_HIGH_LIMIT_62) { + dev_err(di->dev, "battery temp greater them max threshold\n"); + power_supply_changed(&di->btemp_psy); + } + return 0;; +} + +static int ab5500_btemp_auto_temp(struct ab5500_btemp *di) +{ + struct adc_auto_input *auto_ip; + int ret = 0; + + auto_ip = kzalloc(sizeof(struct adc_auto_input), GFP_KERNEL); + if (!auto_ip) { + dev_err(di->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + auto_ip->mux = BTEMP_BALL; + auto_ip->freq = MS500; + auto_ip->min = BTEMP_THERMAL_LOW_LIMIT; + auto_ip->max = BTEMP_THERMAL_HIGH_LIMIT_62; + auto_ip->auto_adc_callback = ab5500_btemp_bat_temp_trig; + di->gpadc_auto = auto_ip; + ret = ab5500_gpadc_convert_auto(di->gpadc, di->gpadc_auto); + if (ret) + dev_err(di->dev, + "failed to set auto trigger for battery temp\n"); + return ret; +} + +#if defined(CONFIG_PM) +static int ab5500_btemp_resume(struct platform_device *pdev) +{ + struct ab5500_btemp *di = platform_get_drvdata(pdev); + + if (di->events.usb_conn) + ab5500_btemp_periodic(di, true); + + return 0; +} + +static int ab5500_btemp_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab5500_btemp *di = platform_get_drvdata(pdev); + + if (di->events.usb_conn) + ab5500_btemp_periodic(di, false); + + return 0; +} +#else +#define ab5500_btemp_suspend NULL +#define ab5500_btemp_resume NULL +#endif + +static int __devexit ab5500_btemp_remove(struct platform_device *pdev) +{ + struct ab5500_btemp *di = platform_get_drvdata(pdev); + int i, irq; + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_btemp_irq[i].name); + free_irq(irq, di); + } + + /* Delete the work queue */ + destroy_workqueue(di->btemp_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->btemp_psy); + platform_set_drvdata(pdev, NULL); + kfree(di->gpadc_auto); + kfree(di); + + return 0; +} + +static int __devinit ab5500_btemp_probe(struct platform_device *pdev) +{ + int irq, i, ret = 0; + struct abx500_bm_plat_data *plat_data; + + struct ab5500_btemp *di = + kzalloc(sizeof(struct ab5500_btemp), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab5500_gpadc_get("ab5500-adc.0"); + + plat_data = pdev->dev.platform_data; + di->pdata = plat_data->btemp; + di->bat = plat_data->battery; + + /* get btemp specific platform data */ + if (!di->pdata) { + dev_err(di->dev, "no btemp platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + /* get battery specific platform data */ + if (!di->bat) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + /* BTEMP supply */ + di->btemp_psy.name = "ab5500_btemp"; + di->btemp_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->btemp_psy.properties = ab5500_btemp_props; + di->btemp_psy.num_properties = ARRAY_SIZE(ab5500_btemp_props); + di->btemp_psy.get_property = ab5500_btemp_get_property; + di->btemp_psy.supplied_to = di->pdata->supplied_to; + di->btemp_psy.num_supplicants = di->pdata->num_supplicants; + di->btemp_psy.external_power_changed = + ab5500_btemp_external_power_changed; + + + /* Create a work queue for the btemp */ + di->btemp_wq = + create_singlethread_workqueue("ab5500_btemp_wq"); + if (di->btemp_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for measuring temperature periodically */ + INIT_DELAYED_WORK_DEFERRABLE(&di->btemp_periodic_work, + ab5500_btemp_periodic_work); + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(di->dev); + if (ret < 0) { + dev_err(di->dev, "failed to get chip ID\n"); + goto free_btemp_wq; + } + di->chip_id = ret; + dev_dbg(di->dev, "ab5500 CID is: 0x%02x\n", + di->chip_id); + + /* Identify the battery */ + if (ab5500_btemp_id(di) < 0) + dev_warn(di->dev, "failed to identify the battery\n"); + + /* Measure temperature once initially */ + di->bat_temp = ab5500_btemp_measure_temp(di); + di->bat->temp_now = di->bat_temp; + + /* Register BTEMP power supply class */ + ret = power_supply_register(di->dev, &di->btemp_psy); + if (ret) { + dev_err(di->dev, "failed to register BTEMP psy\n"); + goto free_btemp_wq; + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_btemp_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab5500_btemp_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab5500_btemp_irq[i].name, di); + + if (ret) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab5500_btemp_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab5500_btemp_irq[i].name, irq, ret); + } + ret = ab5500_btemp_auto_temp(di); + if (ret) { + dev_err(di->dev, + "failed to register auto trigger for battery temp\n"); + goto free_irq; + } + + platform_set_drvdata(pdev, di); + + dev_info(di->dev, "probe success\n"); + return ret; + +free_irq: + power_supply_unregister(&di->btemp_psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab5500_btemp_irq[i].name); + free_irq(irq, di); + } +free_btemp_wq: + destroy_workqueue(di->btemp_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab5500_btemp_driver = { + .probe = ab5500_btemp_probe, + .remove = __devexit_p(ab5500_btemp_remove), + .suspend = ab5500_btemp_suspend, + .resume = ab5500_btemp_resume, + .driver = { + .name = "ab5500-btemp", + .owner = THIS_MODULE, + }, +}; + +static int __init ab5500_btemp_init(void) +{ + return platform_driver_register(&ab5500_btemp_driver); +} + +static void __exit ab5500_btemp_exit(void) +{ + platform_driver_unregister(&ab5500_btemp_driver); +} + +subsys_initcall_sync(ab5500_btemp_init); +module_exit(ab5500_btemp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab5500-btemp"); +MODULE_DESCRIPTION("AB5500 battery temperature driver"); diff --git a/drivers/power/ab5500_charger.c b/drivers/power/ab5500_charger.c new file mode 100644 index 00000000000..4ac35fcb1e8 --- /dev/null +++ b/drivers/power/ab5500_charger.c @@ -0,0 +1,1814 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Charger driver for AB5500 + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/completion.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/workqueue.h> +#include <linux/kobject.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/mfd/abx500/ab5500-bm.h> +#include <linux/mfd/abx500/ab5500-gpadc.h> +#include <linux/mfd/abx500/ux500_chargalg.h> +#include <linux/usb/otg.h> + +/* Charger constants */ +#define NO_PW_CONN 0 +#define USB_PW_CONN 2 + +/* HW failure constants */ +#define VBUS_CH_NOK 0x0A +#define VBUS_OVV_TH 0x06 + +/* AB5500 Charger constants */ +#define AB5500_USB_LINK_STATUS 0x78 +#define CHARGER_REV_SUP 0x10 +#define SW_EOC 0x40 +#define USB_CHAR_DET 0x02 +#define VBUS_RISING 0x20 +#define VBUS_FALLING 0x40 +#define USB_LINK_UPDATE 0x02 +#define USB_CH_TH_PROT_LOW 0x02 +#define USB_CH_TH_PROT_HIGH 0x01 +#define USB_ID_HOST_DET_ENA_MASK 0x02 +#define USB_ID_HOST_DET_ENA 0x02 +#define USB_ID_DEVICE_DET_ENA_MASK 0x01 +#define USB_ID_DEVICE_DET_ENA 0x01 +#define CHARGER_ISET_IN_1_1A 0x0C +#define LED_ENABLE 0x01 +#define RESET 0x00 +#define SSW_ENABLE_REBOOT 0x80 +#define SSW_REBOOT_EN 0x40 +#define SSW_CONTROL_AUTOC 0x04 +#define SSW_PSEL_480S 0x00 + +/* UsbLineStatus register - usb types */ +enum ab5500_charger_link_status { + USB_STAT_NOT_CONFIGURED, + USB_STAT_STD_HOST_NC, + USB_STAT_STD_HOST_C_NS, + USB_STAT_STD_HOST_C_S, + USB_STAT_HOST_CHG_NM, + USB_STAT_HOST_CHG_HS, + USB_STAT_HOST_CHG_HS_CHIRP, + USB_STAT_DEDICATED_CHG, + USB_STAT_ACA_RID_A, + USB_STAT_ACA_RID_B, + USB_STAT_ACA_RID_C_NM, + USB_STAT_ACA_RID_C_HS, + USB_STAT_ACA_RID_C_HS_CHIRP, + USB_STAT_HM_IDGND, + USB_STAT_RESERVED, + USB_STAT_NOT_VALID_LINK, +}; + +enum ab5500_usb_state { + AB5500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */ + AB5500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */ + AB5500_BM_USB_STATE_CONFIGURED, + AB5500_BM_USB_STATE_SUSPEND, + AB5500_BM_USB_STATE_RESUME, + AB5500_BM_USB_STATE_MAX, +}; + +/* VBUS input current limits supported in AB5500 in mA */ +#define USB_CH_IP_CUR_LVL_0P05 50 +#define USB_CH_IP_CUR_LVL_0P09 98 +#define USB_CH_IP_CUR_LVL_0P19 193 +#define USB_CH_IP_CUR_LVL_0P29 290 +#define USB_CH_IP_CUR_LVL_0P38 380 +#define USB_CH_IP_CUR_LVL_0P45 450 +#define USB_CH_IP_CUR_LVL_0P5 500 +#define USB_CH_IP_CUR_LVL_0P6 600 +#define USB_CH_IP_CUR_LVL_0P7 700 +#define USB_CH_IP_CUR_LVL_0P8 800 +#define USB_CH_IP_CUR_LVL_0P9 900 +#define USB_CH_IP_CUR_LVL_1P0 1000 +#define USB_CH_IP_CUR_LVL_1P1 1100 +#define USB_CH_IP_CUR_LVL_1P3 1300 +#define USB_CH_IP_CUR_LVL_1P4 1400 +#define USB_CH_IP_CUR_LVL_1P5 1500 + +#define to_ab5500_charger_usb_device_info(x) container_of((x), \ + struct ab5500_charger, usb_chg) + +/** + * struct ab5500_charger_interrupts - ab5500 interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab5500_charger_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab5500_charger_info { + int charger_connected; + int charger_online; + int charger_voltage; + int cv_active; + bool wd_expired; +}; + +struct ab5500_charger_event_flags { + bool usb_thermal_prot; + bool vbus_ovv; + bool usbchargernotok; + bool vbus_collapse; +}; + +struct ab5500_charger_usb_state { + bool usb_changed; + int usb_current; + enum ab5500_usb_state state; + spinlock_t usb_lock; +}; + +/** + * struct ab5500_charger - ab5500 Charger device information + * @dev: Pointer to the structure device + * @chip_id: Chip-Id of the ab5500 + * @max_usb_in_curr: Max USB charger input current + * @vbus_detected: VBUS detected + * @vbus_detected_start: + * VBUS detected during startup + * @parent: Pointer to the struct ab5500 + * @gpadc: Pointer to the struct gpadc + * @pdata: Pointer to the ab5500_charger platform data + * @bat: Pointer to the ab5500_bm platform data + * @flags: Structure for information about events triggered + * @usb_state: Structure for usb stack information + * @usb_chg: USB charger power supply + * @ac: Structure that holds the AC charger properties + * @usb: Structure that holds the USB charger properties + * @charger_wq: Work queue for the IRQs and checking HW state + * @check_hw_failure_work: Work for checking HW state + * @check_usbchgnotok_work: Work for checking USB charger not ok status + * @ac_work: Work for checking AC charger connection + * @detect_usb_type_work: Work for detecting the USB type connected + * @usb_link_status_work: Work for checking the new USB link status + * @usb_state_changed_work: Work for checking USB state + * @check_main_thermal_prot_work: + * Work for checking Main thermal status + * @check_usb_thermal_prot_work: + * Work for checking USB thermal status + * @ otg: pointer to struct otg_transceiver, used to + * notify the current during a standard host + * charger. + * @nb: structture of type notifier_block, which has + * a function pointer referenced by usb driver. + */ +struct ab5500_charger { + struct device *dev; + u8 chip_id; + int max_usb_in_curr; + bool vbus_detected; + bool vbus_detected_start; + struct ab5500 *parent; + struct ab5500_gpadc *gpadc; + struct abx500_charger_platform_data *pdata; + struct abx500_bm_data *bat; + struct ab5500_charger_event_flags flags; + struct ab5500_charger_usb_state usb_state; + struct ux500_charger usb_chg; + struct ab5500_charger_info usb; + struct workqueue_struct *charger_wq; + struct delayed_work check_hw_failure_work; + struct delayed_work check_usbchgnotok_work; + struct work_struct detect_usb_type_work; + struct work_struct usb_link_status_work; + struct work_struct usb_state_changed_work; + struct work_struct check_usb_thermal_prot_work; + struct otg_transceiver *otg; + struct notifier_block nb; +}; + +/* USB properties */ +static enum power_supply_property ab5500_charger_usb_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/** + * ab5500_charger_get_vbus_voltage() - get vbus voltage + * @di: pointer to the ab5500_charger structure + * + * This function returns the vbus voltage. + * Returns vbus voltage (on success) + */ +static int ab5500_charger_get_vbus_voltage(struct ab5500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->usb.charger_connected) { + vch = ab5500_gpadc_convert(di->gpadc, VBUS_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab5500_charger_get_usb_current() - get usb charger current + * @di: pointer to the ab5500_charger structure + * + * This function returns the usb charger current. + * Returns usb current (on success) and error code on failure + */ +static int ab5500_charger_get_usb_current(struct ab5500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->usb.charger_online) { + ich = ab5500_gpadc_convert(di->gpadc, USB_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab5500_charger_detect_chargers() - Detect the connected chargers + * @di: pointer to the ab5500_charger structure + * + * Returns the type of charger connected. + * For USB it will not mean we can actually charge from it + * but that there is a USB cable connected that we have to + * identify. This is used during startup when we don't get + * interrupts of the charger detection + * + * Returns an integer value, that means, + * NO_PW_CONN no power supply is connected + * USB_PW_CONN if the USB power supply is connected + */ +static int ab5500_charger_detect_chargers(struct ab5500_charger *di) +{ + int result = NO_PW_CONN; + int ret; + u8 val; + /* Check for USB charger */ + /* + * TODO: Since there are no status register validating by + * reading the IT souce registers + */ + ret = abx500_get_register_interruptible(di->dev, AB5500_BANK_IT, + AB5500_IT_SOURCE8, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return ret; + } + + if (val & VBUS_RISING) + result |= USB_PW_CONN; + else if (val & VBUS_FALLING) + result = NO_PW_CONN; + + return result; +} + +/** + * ab5500_charger_max_usb_curr() - get the max curr for the USB type + * @di: pointer to the ab5500_charger structure + * @link_status: the identified USB type + * + * Get the maximum current that is allowed to be drawn from the host + * based on the USB type. + * Returns error code in case of failure else 0 on success + */ +static int ab5500_charger_max_usb_curr(struct ab5500_charger *di, + enum ab5500_charger_link_status link_status) +{ + int ret = 0; + + switch (link_status) { + case USB_STAT_STD_HOST_NC: + case USB_STAT_STD_HOST_C_NS: + case USB_STAT_STD_HOST_C_S: + dev_dbg(di->dev, "USB Type - Standard host is " + "detected through USB driver\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09; + break; + case USB_STAT_HOST_CHG_HS_CHIRP: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + break; + case USB_STAT_HOST_CHG_HS: + case USB_STAT_ACA_RID_C_HS: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P9; + break; + case USB_STAT_ACA_RID_A: + /* + * Dedicated charger level minus maximum current accessory + * can consume (300mA). Closest level is 1100mA + */ + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P1; + break; + case USB_STAT_ACA_RID_B: + /* + * Dedicated charger level minus 120mA (20mA for ACA and + * 100mA for potential accessory). Closest level is 1300mA + */ + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P3; + break; + case USB_STAT_DEDICATED_CHG: + case USB_STAT_HOST_CHG_NM: + case USB_STAT_ACA_RID_C_HS_CHIRP: + case USB_STAT_ACA_RID_C_NM: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5; + break; + case USB_STAT_RESERVED: + /* + * This state is used to indicate that VBUS has dropped below + * the detection level 4 times in a row. This is due to the + * charger output current is set to high making the charger + * voltage collapse. This have to be propagated through to + * chargalg. This is done using the property + * POWER_SUPPLY_PROP_CURRENT_AVG = 1 + */ + di->flags.vbus_collapse = true; + dev_dbg(di->dev, "USB Type - USB_STAT_RESERVED " + "VBUS has collapsed\n"); + ret = -1; + break; + case USB_STAT_HM_IDGND: + case USB_STAT_NOT_CONFIGURED: + case USB_STAT_NOT_VALID_LINK: + dev_err(di->dev, "USB Type - Charging not allowed\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + default: + dev_err(di->dev, "USB Type - Unknown\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + }; + + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", + link_status, di->max_usb_in_curr); + + return ret; +} + +/** + * ab5500_charger_read_usb_type() - read the type of usb connected + * @di: pointer to the ab5500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab5500_charger_read_usb_type(struct ab5500_charger *di) +{ + int ret; + u8 val; + + ret = abx500_get_register_interruptible(di->dev, AB5500_BANK_USB, + AB5500_USB_LINE_STATUS, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return ret; + } + + /* get the USB type */ + val = (val & AB5500_USB_LINK_STATUS) >> 3; + ret = ab5500_charger_max_usb_curr(di, + (enum ab5500_charger_link_status) val); + + return ret; +} + +static int ab5500_charger_voltage_map[] = { + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3875 , + 3900 , + 3925 , + 3950 , + 3975 , + 4000 , + 4025 , + 4050 , + 4060 , + 4070 , + 4080 , + 4090 , + 4100 , + 4110 , + 4120 , + 4130 , + 4140 , + 4150 , + 4160 , + 4170 , + 4180 , + 4190 , + 4200 , + 4210 , + 4220 , + 4230 , + 4240 , + 4250 , + 4260 , + 4270 , + 4280 , + 4290 , + 4300 , + 4310 , + 4320 , + 4330 , + 4340 , + 4350 , + 4360 , + 4370 , + 4380 , + 4390 , + 4400 , + 4410 , + 4420 , + 4430 , + 4440 , + 4450 , + 4460 , + 4470 , + 4480 , + 4490 , + 4500 , + 4510 , + 4520 , + 4530 , + 4540 , + 4550 , + 4560 , + 4570 , + 4580 , + 4590 , + 4600 , +}; + +/* + * This array maps the raw hex value to charger current used by the ab5500 + * Values taken from the AB5500 product specification manual + */ +static int ab5500_charger_current_map[] = { + 100 , + 200 , + 300 , + 400 , + 500 , + 600 , + 700 , + 800 , + 900 , + 1000, + 1100, + 1200, + 1300, + 1400, + 1500, + 1500, +}; + +static int ab5500_icsr_current_map[] = { + 50, + 93, + 193, + 290, + 380, + 450, + 500 , + 600 , + 700 , + 800 , + 900 , + 1000, + 1100, + 1300, + 1400, + 1500, +}; + +static int ab5500_cvrec_voltage_map[] = { + 3300, + 3325, + 3350, + 3375, + 3400, + 3425, + 3450, + 3475, + 3500, + 3525, + 3550, + 3575, + 3600, + 3625, + 3650, + 3675, + 3700, + 3725, + 3750, + 3775, + 3800, + 3825, + 3850, + 3875, + 3900, + 3925, + 4000, + 4025, + 4050, + 4075, + 4100, + 4125, + 4150, + 4175, + 4200, + 4225, + 4250, + 4275, + 4300, + 4325, + 4350, + 4375, + 4400, + 4425, + 4450, + 4475, + 4500, + 4525, + 4550, + 4575, + 4600, +}; + +static int ab5500_cvrec_voltage_to_regval(int voltage) +{ + int i; + + /* Special case for voltage below 3.3V */ + if (voltage < ab5500_cvrec_voltage_map[0]) + return 0; + + for (i = 1; i < ARRAY_SIZE(ab5500_cvrec_voltage_map); i++) { + if (voltage < ab5500_cvrec_voltage_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab5500_cvrec_voltage_map) - 1; + if (voltage == ab5500_cvrec_voltage_map[i]) + return i; + else + return -1; +} + +static int ab5500_voltage_to_regval(int voltage) +{ + int i; + + /* Special case for voltage below 3.3V */ + if (voltage < ab5500_charger_voltage_map[0]) + return 0; + + for (i = 1; i < ARRAY_SIZE(ab5500_charger_voltage_map); i++) { + if (voltage < ab5500_charger_voltage_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab5500_charger_voltage_map) - 1; + if (voltage == ab5500_charger_voltage_map[i]) + return i; + else + return -1; +} + +static int ab5500_icsr_curr_to_regval(int curr) +{ + int i; + + if (curr < ab5500_icsr_current_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab5500_icsr_current_map); i++) { + if (curr < ab5500_icsr_current_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab5500_icsr_current_map) - 1; + if (curr == ab5500_icsr_current_map[i]) + return i; + else + return -1; +} + +static int ab5500_current_to_regval(int curr) +{ + int i; + + if (curr < ab5500_charger_current_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab5500_charger_current_map); i++) { + if (curr < ab5500_charger_current_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab5500_charger_current_map) - 1; + if (curr == ab5500_charger_current_map[i]) + return i; + else + return -1; +} + +/** + * ab5500_charger_get_usb_cur() - get usb current + * @di: pointer to the ab5500_charger structre + * + * The usb stack provides the maximum current that can be drawn from + * the standard usb host. This will be in mA. + * This function converts current in mA to a value that can be written + * to the register. Returns -1 if charging is not allowed + */ +static int ab5500_charger_get_usb_cur(struct ab5500_charger *di) +{ + switch (di->usb_state.usb_current) { + case 50: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + break; + case 100: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09; + break; + case 200: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P19; + break; + case 300: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P29; + break; + case 400: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P38; + break; + case 500: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + break; + default: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + return -1; + break; + }; + return 0; +} + +/** + * ab5500_charger_set_vbus_in_curr() - set VBUS input current limit + * @di: pointer to the ab5500_charger structure + * @ich_in: charger input current limit + * + * Sets the current that can be drawn from the USB host + * Returns error code in case of failure else 0(on success) + */ +static int ab5500_charger_set_vbus_in_curr(struct ab5500_charger *di, + int ich_in) +{ + int ret; + int input_curr_index; + int min_value; + + /* We should always use to lowest current limit */ + min_value = min(di->bat->chg_params->usb_curr_max, ich_in); + + input_curr_index = ab5500_icsr_curr_to_regval(min_value); + if (input_curr_index < 0) { + dev_err(di->dev, "VBUS input current limit too high\n"); + return -ENXIO; + } + + ret = abx500_set_register_interruptible(di->dev, AB5500_BANK_CHG, + AB5500_ICSR, input_curr_index); + if (ret) + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + + return ret; +} + +/** + * ab5500_charger_usb_en() - enable usb charging + * @di: pointer to the ab5500_charger structure + * @enable: enable/disable flag + * @vset: charging voltage + * @ich_out: charger output current + * + * Enable/Disable USB charging and turns on/off the charging led respectively. + * Returns error code in case of failure else 0(on success) + */ +static int ab5500_charger_usb_en(struct ux500_charger *charger, + int enable, int vset, int ich_out) +{ + int ret; + int volt_index; + int curr_index; + + struct ab5500_charger *di = to_ab5500_charger_usb_device_info(charger); + + if (enable) { + /* Check if USB is connected */ + if (!di->usb.charger_connected) { + dev_err(di->dev, "USB charger not connected\n"); + return -ENXIO; + } + + /* Enable USB charging */ + dev_dbg(di->dev, "Enable USB: %dmV %dmA\n", vset, ich_out); + + volt_index = ab5500_voltage_to_regval(vset); + curr_index = ab5500_current_to_regval(ich_out) ; + + /* ChVoltLevel: max voltage upto which battery can be charged */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_VSRC, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + + /* current that can be drawn from the usb */ + ret = ab5500_charger_set_vbus_in_curr(di, ich_out); + if (ret) { + dev_err(di->dev, "%s setting icsr failed %d\n", + __func__, __LINE__); + return ret; + } + + /* ChOutputCurentLevel: protected output current */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_OCSRV, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + + /* + * Battery voltage when charging should be resumed after + * completion of charging + */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_CVREC, + ab5500_cvrec_voltage_to_regval( + di->bat->bat_type[di->bat->batt_id].recharge_vol)); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + /* + * Battery temperature: + * Input to the TBDATA register corresponds to the battery + * temperature(temp being multiples of 2) + * In order to obatain the value to be written to this reg + * divide the temperature obtained from gpadc by 2 + */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_TBDATA, + di->bat->temp_now / 2); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + + /* If success power on charging LED indication */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_LEDT, LED_ENABLE); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + + /* + * Register DCIOCURRENT is one among the charging watchdog + * rekick sequence, hence irrespective of usb charging this + * register will have to be written. + */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_DCIOCURRENT, + RESET); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + + di->usb.charger_online = 1; + } else { + /* ChVoltLevel: max voltage upto which battery can be charged */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_VSRC, RESET); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + /* USBChInputCurr: current that can be drawn from the usb */ + ret = ab5500_charger_set_vbus_in_curr(di, RESET); + if (ret) { + dev_err(di->dev, "%s resetting icsr failed %d\n", + __func__, __LINE__); + return ret; + } + /* If success power off charging LED indication */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_LEDT, RESET); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + di->usb.charger_online = 0; + di->usb.wd_expired = false; + dev_dbg(di->dev, "%s Disabled USB charging\n", __func__); + } + power_supply_changed(&di->usb_chg.psy); + + return ret; +} + +/** + * ab5500_charger_watchdog_kick() - kick charger watchdog + * @di: pointer to the ab5500_charger structure + * + * Kick charger watchdog + * Returns error code in case of failure else 0(on success) + */ +static int ab5500_charger_watchdog_kick(struct ux500_charger *charger) +{ + int ret; + struct ab5500_charger *di; + int volt_index, curr_index; + u8 value = 0; + + /* TODO: update */ + if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab5500_charger_usb_device_info(charger); + else + return -ENXIO; + + ret = abx500_get_register_interruptible(di->dev, AB5500_BANK_STARTUP, + AB5500_MCB, &value); + if (ret) + dev_err(di->dev, "Failed to read!\n"); + + value = value | (SSW_ENABLE_REBOOT | SSW_REBOOT_EN | + SSW_CONTROL_AUTOC | SSW_PSEL_480S); + ret = abx500_set_register_interruptible(di->dev, AB5500_BANK_STARTUP, + AB5500_MCB, value); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + volt_index = ab5500_voltage_to_regval( + di->bat->bat_type[di->bat->batt_id].normal_vol_lvl); + curr_index = ab5500_current_to_regval(di->max_usb_in_curr); + + /* ChVoltLevel: max voltage upto which battery can be charged */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_VSRC, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + return ret; + } + + /* current that can be drawn from the usb */ + ret = ab5500_charger_set_vbus_in_curr(di, di->max_usb_in_curr); + if (ret) { + dev_err(di->dev, "%s setting icsr failed %d\n", + __func__, __LINE__); + return ret; + } + + /* ChOutputCurentLevel: protected output current */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_OCSRV, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + return ret; + } + + /* + * Battery voltage when charging should be resumed after + * completion of charging + */ + /* Charger_Vrechar[5:0] = '4.025 V' */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_CVREC, + ab5500_cvrec_voltage_to_regval( + di->bat->bat_type[di->bat->batt_id].recharge_vol)); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + return ret; + } + /* + * Battery temperature: + * Input to the TBDATA register corresponds to the battery + * temperature(temp being multiples of 2) + * In order to obatain the value to be written to this reg + * divide the temperature obtained from gpadc by 2 + */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_TBDATA, + di->bat->temp_now / 2); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + return ret; + } + /* + * Register DCIOCURRENT is one among the charging watchdog + * rekick sequence, hence irrespective of usb charging this + * register will have to be written. + */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_DCIOCURRENT, + RESET); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + return ret; + } + + return ret; +} + +/** + * ab5500_charger_update_charger_current() - update charger current + * @di: pointer to the ab5500_charger structure + * + * Update the charger output current for the specified charger + * Returns error code in case of failure else 0(on success) + */ +static int ab5500_charger_update_charger_current(struct ux500_charger *charger, + int ich_out) +{ + int ret = 0; + int curr_index; + struct ab5500_charger *di; + + if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab5500_charger_usb_device_info(charger); + else + return -ENXIO; + + curr_index = ab5500_current_to_regval(ich_out); + if (curr_index < 0) { + dev_err(di->dev, + "Charger current too high, " + "charging not started\n"); + return -ENXIO; + } + + ret = abx500_set_register_interruptible(di->dev, AB5500_BANK_CHG, + AB5500_OCSRV, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + return ret; + } + + return ret; +} + +/** + * ab5500_charger_check_hw_failure_work() - check main charger failure + * @work: pointer to the work_struct structure + * + * Work queue function for checking the main charger status + */ +static void ab5500_charger_check_hw_failure_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab5500_charger *di = container_of(work, + struct ab5500_charger, check_hw_failure_work.work); + + /* Check if the status bits for HW failure is still active */ + if (di->flags.vbus_ovv) { + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_USB_PHY_STATUS, + ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return; + } + if (!(reg_value & VBUS_OVV_TH)) { + di->flags.vbus_ovv = false; + power_supply_changed(&di->usb_chg.psy); + } + } + /* If we still have a failure, schedule a new check */ + if (di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, round_jiffies(HZ)); + } +} + +/** + * ab5500_charger_detect_usb_type_work() - work to detect USB type + * @work: Pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +void ab5500_charger_detect_usb_type_work(struct work_struct *work) +{ + int ret; + + struct ab5500_charger *di = container_of(work, + struct ab5500_charger, detect_usb_type_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab5500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + di->vbus_detected = 0; + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } else { + di->vbus_detected = 1; + } +} + +/** + * ab5500_charger_usb_link_status_work() - work to detect USB type + * @work: pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +static void ab5500_charger_usb_link_status_work(struct work_struct *work) +{ + int ret; + + struct ab5500_charger *di = container_of(work, + struct ab5500_charger, usb_link_status_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab5500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + di->vbus_detected = 0; + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } else { + di->vbus_detected = 1; + ret = ab5500_charger_read_usb_type(di); + if (!ret) { + /* Update maximum input current */ + ret = ab5500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr); + if (ret) + return; + + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } else if (ret == -ENXIO) { + /* No valid charger type detected */ + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } + } +} + +static void ab5500_charger_usb_state_changed_work(struct work_struct *work) +{ + int ret; + unsigned long flags; + struct ab5500_charger *di = container_of(work, + struct ab5500_charger, usb_state_changed_work); + + if (!di->vbus_detected) + return; + + spin_lock_irqsave(&di->usb_state.usb_lock, flags); + di->usb_state.usb_changed = false; + spin_unlock_irqrestore(&di->usb_state.usb_lock, flags); + + /* + * wait for some time until you get updates from the usb stack + * and negotiations are completed + */ + msleep(250); + + if (di->usb_state.usb_changed) + return; + + dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n", + __func__, di->usb_state.state, di->usb_state.usb_current); + + switch (di->usb_state.state) { + case AB5500_BM_USB_STATE_RESET_HS: + case AB5500_BM_USB_STATE_RESET_FS: + case AB5500_BM_USB_STATE_SUSPEND: + case AB5500_BM_USB_STATE_MAX: + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + break; + + case AB5500_BM_USB_STATE_RESUME: + /* + * when suspend->resume there should be delay + * of 1sec for enabling charging + */ + msleep(1000); + /* Intentional fall through */ + case AB5500_BM_USB_STATE_CONFIGURED: + /* + * USB is configured, enable charging with the charging + * input current obtained from USB driver + */ + if (!ab5500_charger_get_usb_cur(di)) { + /* Update maximum input current */ + ret = ab5500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr); + if (ret) + return; + + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } + break; + + default: + break; + }; +} + +/** + * ab5500_charger_check_usbchargernotok_work() - check USB chg not ok status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB charger Not OK status + */ +static void ab5500_charger_check_usbchargernotok_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + bool prev_status; + + struct ab5500_charger *di = container_of(work, + struct ab5500_charger, check_usbchgnotok_work.work); + + /* Check if the status bit for usbchargernotok is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_CHGFSM_CHARGER_DETECT, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return; + } + prev_status = di->flags.usbchargernotok; + + if (reg_value & VBUS_CH_NOK) { + di->flags.usbchargernotok = true; + /* Check again in 1sec */ + queue_delayed_work(di->charger_wq, + &di->check_usbchgnotok_work, HZ); + } else { + di->flags.usbchargernotok = false; + di->flags.vbus_collapse = false; + } + + if (prev_status != di->flags.usbchargernotok) + power_supply_changed(&di->usb_chg.psy); +} + +/** + * ab5500_charger_check_usb_thermal_prot_work() - check usb thermal status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB thermal prot status + */ +static void ab5500_charger_check_usb_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab5500_charger *di = container_of(work, + struct ab5500_charger, check_usb_thermal_prot_work); + + /* Check if the status bit for usb_thermal_prot is still active */ + /* TODO: Interrupt source reg 15 bit 4 */ + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_CHGFSM_USB_BTEMP_CURR_LIM, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return; + } + if (reg_value & USB_CH_TH_PROT_LOW || reg_value & USB_CH_TH_PROT_HIGH) + di->flags.usb_thermal_prot = true; + else + di->flags.usb_thermal_prot = false; + + power_supply_changed(&di->usb_chg.psy); +} + +/** + * ab5500_charger_vbusdetf_handler() - VBUS falling detected + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_vbusdetf_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + dev_dbg(di->dev, "VBUS falling detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_vbusdetr_handler() - VBUS rising detected + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_vbusdetr_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + di->vbus_detected = true; + dev_dbg(di->dev, "VBUS rising detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_usblinkstatus_handler() - USB link status has changed + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_usblinkstatus_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + dev_dbg(di->dev, "USB link status changed\n"); + + queue_work(di->charger_wq, &di->usb_link_status_work); + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_usbchthprotr_handler() - Die temp is above usb charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_usbchthprotr_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp above USB charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_usbchargernotokr_handler() - USB charger not ok detected + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_usbchargernotokr_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + dev_dbg(di->dev, "Not allowed USB charger detected\n"); + queue_delayed_work(di->charger_wq, &di->check_usbchgnotok_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_chwdexp_handler() - Charger watchdog expired + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_chwdexp_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + dev_dbg(di->dev, "Charger watchdog expired\n"); + + /* + * The charger that was online when the watchdog expired + * needs to be restarted for charging to start again + */ + if (di->usb.charger_online) { + di->usb.wd_expired = true; + power_supply_changed(&di->usb_chg.psy); + } + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_vbusovv_handler() - VBUS overvoltage detected + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_vbusovv_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + dev_dbg(di->dev, "VBUS overvoltage detected\n"); + di->flags.vbus_ovv = true; + power_supply_changed(&di->usb_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_usb_get_property() - get the usb properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the usb + * properties by reading the sysfs files. + * USB properties are online, present and voltage. + * online: usb charging is in progress or not + * present: presence of the usb + * voltage: vbus voltage + * Returns error code in case of failure else 0(on success) + */ +static int ab5500_charger_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab5500_charger *di; + + di = to_ab5500_charger_usb_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (di->flags.usbchargernotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (di->usb.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (di->flags.usb_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (di->flags.vbus_ovv) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->usb.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->usb.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + di->usb.charger_voltage = ab5500_charger_get_vbus_voltage(di); + val->intval = di->usb.charger_voltage * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ab5500_charger_get_usb_current(di) * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + /* + * This property is used to indicate when VBUS has collapsed + * due to too high output current from the USB charger + */ + if (di->flags.vbus_collapse) + val->intval = 1; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * ab5500_charger_hw_registers() - Set up charger related registers + * @di: pointer to the ab5500_charger structure + * + * Set up charger OVV, watchdog and maximum voltage registers as well as + * charging of the backup battery + */ +static int ab5500_charger_init_hw_registers(struct ab5500_charger *di) +{ + int ret = 0; + + /* Enable ID Host and Device detection */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_USB_OTG_CTRL, + USB_ID_HOST_DET_ENA_MASK, USB_ID_HOST_DET_ENA); + if (ret) { + dev_err(di->dev, "failed to enable usb charger detection\n"); + goto out; + } + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_USB_OTG_CTRL, + USB_ID_DEVICE_DET_ENA_MASK, USB_ID_DEVICE_DET_ENA); + if (ret) { + dev_err(di->dev, "failed to enable usb charger detection\n"); + goto out; + } + + /* Over current protection for reverse supply */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_CREVS, CHARGER_REV_SUP, + CHARGER_REV_SUP); + if (ret) { + dev_err(di->dev, + "failed to enable over current protection for reverse supply\n"); + goto out; + } + + /* Enable SW EOC at flatcurrent detection */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_CCTRL, SW_EOC, SW_EOC); + if (ret) { + dev_err(di->dev, + "failed to enable end of charge at flatcurrent detection\n"); + goto out; + } +out: + return ret; +} + +/* + * ab5500 charger driver interrupts and their respective isr + */ +static struct ab5500_charger_interrupts ab5500_charger_irq[] = { + {"VBUS_FALLING", ab5500_charger_vbusdetf_handler}, + {"VBUS_RISING", ab5500_charger_vbusdetr_handler}, + {"USB_LINK_UPDATE", ab5500_charger_usblinkstatus_handler}, + {"USB_CH_TH_PROTECTION", ab5500_charger_usbchthprotr_handler}, + {"USB_CH_NOT_OK", ab5500_charger_usbchargernotokr_handler}, + {"OVV", ab5500_charger_vbusovv_handler}, + /* TODO: Interrupt missing, will be available in cut 2 */ + /*{"CHG_SW_TIMER_OUT", ab5500_charger_chwdexp_handler},*/ +}; + +static int ab5500_charger_usb_notifier_call(struct notifier_block *nb, + unsigned long event, void *power) +{ + struct ab5500_charger *di = + container_of(nb, struct ab5500_charger, nb); + enum ab5500_usb_state bm_usb_state; + unsigned mA = *((unsigned *)power); + + /* TODO: State is fabricate here. See if charger really needs USB + * state or if mA is enough + */ + if ((di->usb_state.usb_current == 2) && (mA > 2)) + bm_usb_state = AB5500_BM_USB_STATE_RESUME; + else if (mA == 0) + bm_usb_state = AB5500_BM_USB_STATE_RESET_HS; + else if (mA == 2) + bm_usb_state = AB5500_BM_USB_STATE_SUSPEND; + else if (mA >= 8) /* 8, 100, 500 */ + bm_usb_state = AB5500_BM_USB_STATE_CONFIGURED; + else /* Should never occur */ + bm_usb_state = AB5500_BM_USB_STATE_RESET_FS; + + dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n", + __func__, bm_usb_state, mA); + + spin_lock(&di->usb_state.usb_lock); + di->usb_state.usb_changed = true; + di->usb_state.state = bm_usb_state; + di->usb_state.usb_current = mA; + spin_unlock(&di->usb_state.usb_lock); + + queue_work(di->charger_wq, &di->usb_state_changed_work); + + return NOTIFY_OK; +} + +#if defined(CONFIG_PM) +static int ab5500_charger_resume(struct platform_device *pdev) +{ + struct ab5500_charger *di = platform_get_drvdata(pdev); + + /* If we still have a HW failure, schedule a new check */ + if (di->flags.usbchargernotok || di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, 0); + } + + return 0; +} + +static int ab5500_charger_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab5500_charger *di = platform_get_drvdata(pdev); + + /* Cancel any pending HW failure check */ + if (delayed_work_pending(&di->check_hw_failure_work)) + cancel_delayed_work(&di->check_hw_failure_work); + + return 0; +} +#else +#define ab5500_charger_suspend NULL +#define ab5500_charger_resume NULL +#endif + +static int __devexit ab5500_charger_remove(struct platform_device *pdev) +{ + struct ab5500_charger *di = platform_get_drvdata(pdev); + int i, irq; + + /* Disable USB charging */ + ab5500_charger_usb_en(&di->usb_chg, false, 0, 0); + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_charger_irq[i].name); + free_irq(irq, di); + } + + otg_unregister_notifier(di->otg, &di->nb); + otg_put_transceiver(di->otg); + + /* Delete the work queue */ + destroy_workqueue(di->charger_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->usb_chg.psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab5500_charger_probe(struct platform_device *pdev) +{ + int irq, i, charger_status, ret = 0; + struct abx500_bm_plat_data *plat_data; + + struct ab5500_charger *di = + kzalloc(sizeof(struct ab5500_charger), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab5500_gpadc_get("ab5500-adc.0"); + + /* initialize lock */ + spin_lock_init(&di->usb_state.usb_lock); + + plat_data = pdev->dev.platform_data; + di->pdata = plat_data->charger; + di->bat = plat_data->battery; + + /* get charger specific platform data */ + if (!di->pdata) { + dev_err(di->dev, "no charger platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + /* get battery specific platform data */ + if (!di->bat) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + /* USB supply */ + /* power_supply base class */ + di->usb_chg.psy.name = "ab5500_usb"; + di->usb_chg.psy.type = POWER_SUPPLY_TYPE_USB; + di->usb_chg.psy.properties = ab5500_charger_usb_props; + di->usb_chg.psy.num_properties = ARRAY_SIZE(ab5500_charger_usb_props); + di->usb_chg.psy.get_property = ab5500_charger_usb_get_property; + di->usb_chg.psy.supplied_to = di->pdata->supplied_to; + di->usb_chg.psy.num_supplicants = di->pdata->num_supplicants; + /* ux500_charger sub-class */ + di->usb_chg.ops.enable = &ab5500_charger_usb_en; + di->usb_chg.ops.kick_wd = &ab5500_charger_watchdog_kick; + di->usb_chg.ops.update_curr = &ab5500_charger_update_charger_current; + di->usb_chg.max_out_volt = ab5500_charger_voltage_map[ + ARRAY_SIZE(ab5500_charger_voltage_map) - 1]; + di->usb_chg.max_out_curr = ab5500_charger_current_map[ + ARRAY_SIZE(ab5500_charger_current_map) - 1]; + + + /* Create a work queue for the charger */ + di->charger_wq = + create_singlethread_workqueue("ab5500_charger_wq"); + if (di->charger_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for HW failure check */ + INIT_DELAYED_WORK_DEFERRABLE(&di->check_hw_failure_work, + ab5500_charger_check_hw_failure_work); + INIT_DELAYED_WORK_DEFERRABLE(&di->check_usbchgnotok_work, + ab5500_charger_check_usbchargernotok_work); + + /* Init work for charger detection */ + INIT_WORK(&di->usb_link_status_work, + ab5500_charger_usb_link_status_work); + INIT_WORK(&di->detect_usb_type_work, + ab5500_charger_detect_usb_type_work); + + INIT_WORK(&di->usb_state_changed_work, + ab5500_charger_usb_state_changed_work); + + /* Init work for checking HW status */ + INIT_WORK(&di->check_usb_thermal_prot_work, + ab5500_charger_check_usb_thermal_prot_work); + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(di->dev); + if (ret < 0) { + dev_err(di->dev, "failed to get chip ID\n"); + goto free_charger_wq; + } + di->chip_id = ret; + dev_dbg(di->dev, "AB5500 CID is: 0x%02x\n", di->chip_id); + + /* Initialize OVV, and other registers */ + ret = ab5500_charger_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize ABB registers\n"); + goto free_device_info; + } + + /* Register USB charger class */ + ret = power_supply_register(di->dev, &di->usb_chg.psy); + if (ret) { + dev_err(di->dev, "failed to register USB charger\n"); + goto free_device_info; + } + + di->otg = otg_get_transceiver(); + if (!di->otg) { + dev_err(di->dev, "failed to get otg transceiver\n"); + goto free_usb; + } + di->nb.notifier_call = ab5500_charger_usb_notifier_call; + ret = otg_register_notifier(di->otg, &di->nb); + if (ret) { + dev_err(di->dev, "failed to register otg notifier\n"); + goto put_otg_transceiver; + } + + /* Identify the connected charger types during startup */ + charger_status = ab5500_charger_detect_chargers(di); + if (charger_status & USB_PW_CONN) { + dev_dbg(di->dev, "VBUS Detect during startup\n"); + di->vbus_detected = true; + di->vbus_detected_start = true; + queue_work(di->charger_wq, + &di->usb_link_status_work); + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_charger_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab5500_charger_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab5500_charger_irq[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab5500_charger_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab5500_charger_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + dev_info(di->dev, "probe success\n"); + return ret; + +free_irq: + otg_unregister_notifier(di->otg, &di->nb); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab5500_charger_irq[i].name); + free_irq(irq, di); + } +put_otg_transceiver: + otg_put_transceiver(di->otg); +free_usb: + power_supply_unregister(&di->usb_chg.psy); +free_charger_wq: + destroy_workqueue(di->charger_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab5500_charger_driver = { + .probe = ab5500_charger_probe, + .remove = __devexit_p(ab5500_charger_remove), + .suspend = ab5500_charger_suspend, + .resume = ab5500_charger_resume, + .driver = { + .name = "ab5500-charger", + .owner = THIS_MODULE, + }, +}; + +static int __init ab5500_charger_init(void) +{ + return platform_driver_register(&ab5500_charger_driver); +} + +static void __exit ab5500_charger_exit(void) +{ + platform_driver_unregister(&ab5500_charger_driver); +} + +subsys_initcall_sync(ab5500_charger_init); +module_exit(ab5500_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab5500-charger"); +MODULE_DESCRIPTION("AB5500 charger management driver"); diff --git a/drivers/power/ab5500_fg.c b/drivers/power/ab5500_fg.c new file mode 100644 index 00000000000..62710680d1e --- /dev/null +++ b/drivers/power/ab5500_fg.c @@ -0,0 +1,1838 @@ +/* + * Copyright (C) ST-Ericsson AB 2011 + * + * Main and Back-up battery management driver. + * + * Note: Backup battery management is required in case of Li-Ion battery and not + * for capacitive battery. HREF boards have capacitive battery and hence backup + * battery management is not used and the supported code is available in this + * driver. + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/mfd/abx500/ab5500-gpadc.h> +#include <linux/mfd/abx500/ab5500-bm.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> + +static LIST_HEAD(ab5500_fg_list); + +/* U5500 Constants */ +#define FG_ON_MASK 0x04 +#define FG_ON 0x04 +#define FG_ACC_RESET_ON_READ_MASK 0x08 +#define FG_ACC_RESET_ON_READ 0x08 +#define EN_READOUT_MASK 0x01 +#define EN_READOUT 0x01 +#define RESET 0x00 +#define EOC_52_mA 0x04 +#define MILLI_TO_MICRO 1000 +#define FG_LSB_IN_MA 770 +#define QLSB_NANO_AMP_HOURS_X10 1129 +#define SEC_TO_SAMPLE(S) (S * 4) +#define NBR_AVG_SAMPLES 20 +#define LOW_BAT_CHECK_INTERVAL (2 * HZ) + +#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */ + +#define interpolate(x, x1, y1, x2, y2) \ + ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1)))); + +#define to_ab5500_fg_device_info(x) container_of((x), \ + struct ab5500_fg, fg_psy); + +/** + * struct ab5500_fg_interrupts - ab5500 fg interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab5500_fg_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +enum ab5500_fg_discharge_state { + AB5500_FG_DISCHARGE_INIT, + AB5500_FG_DISCHARGE_INITMEASURING, + AB5500_FG_DISCHARGE_INIT_RECOVERY, + AB5500_FG_DISCHARGE_RECOVERY, + AB5500_FG_DISCHARGE_READOUT, + AB5500_FG_DISCHARGE_WAKEUP, +}; + +static char *discharge_state[] = { + "DISCHARGE_INIT", + "DISCHARGE_INITMEASURING", + "DISCHARGE_INIT_RECOVERY", + "DISCHARGE_RECOVERY", + "DISCHARGE_READOUT", + "DISCHARGE_WAKEUP", +}; + +enum ab5500_fg_charge_state { + AB5500_FG_CHARGE_INIT, + AB5500_FG_CHARGE_READOUT, +}; + +static char *charge_state[] = { + "CHARGE_INIT", + "CHARGE_READOUT", +}; + +enum ab5500_fg_calibration_state { + AB5500_FG_CALIB_INIT, + AB5500_FG_CALIB_WAIT, + AB5500_FG_CALIB_END, +}; + +struct ab5500_fg_avg_cap { + int avg; + int samples[NBR_AVG_SAMPLES]; + __kernel_time_t time_stamps[NBR_AVG_SAMPLES]; + int pos; + int nbr_samples; + int sum; +}; + +struct ab5500_fg_battery_capacity { + int max_mah_design; + int max_mah; + int mah; + int permille; + int level; + int prev_mah; + int prev_percent; + int prev_level; +}; + +struct ab5500_fg_flags { + bool fg_enabled; + bool conv_done; + bool charging; + bool fully_charged; + bool low_bat_delay; + bool low_bat; + bool bat_ovv; + bool batt_unknown; + bool calibrate; +}; + +/** + * struct ab5500_fg - ab5500 FG device information + * @dev: Pointer to the structure device + * @vbat: Battery voltage in mV + * @vbat_nom: Nominal battery voltage in mV + * @inst_curr: Instantenous battery current in mA + * @avg_curr: Average battery current in mA + * @fg_samples: Number of samples used in the FG accumulation + * @accu_charge: Accumulated charge from the last conversion + * @recovery_cnt: Counter for recovery mode + * @high_curr_cnt: Counter for high current mode + * @init_cnt: Counter for init mode + * @v_to_cap: capacity based on battery voltage + * @recovery_needed: Indicate if recovery is needed + * @high_curr_mode: Indicate if we're in high current mode + * @init_capacity: Indicate if initial capacity measuring should be done + * @calib_state State during offset calibration + * @discharge_state: Current discharge state + * @charge_state: Current charge state + * @flags: Structure for information about events triggered + * @bat_cap: Structure for battery capacity specific parameters + * @avg_cap: Average capacity filter + * @parent: Pointer to the struct ab5500 + * @gpadc: Pointer to the struct gpadc + * @gpadc_auto: Pointer tot he struct adc_auto_input + * @pdata: Pointer to the ab5500_fg platform data + * @bat: Pointer to the ab5500_bm platform data + * @fg_psy: Structure that holds the FG specific battery properties + * @fg_wq: Work queue for running the FG algorithm + * @fg_periodic_work: Work to run the FG algorithm periodically + * @fg_low_bat_work: Work to check low bat condition + * @fg_work: Work to run the FG algorithm instantly + * @fg_acc_cur_work: Work to read the FG accumulator + * @cc_lock: Mutex for locking the CC + * @node: struct of type list_head + */ +struct ab5500_fg { + struct device *dev; + int vbat; + int vbat_nom; + int inst_curr; + int avg_curr; + int fg_samples; + int accu_charge; + int recovery_cnt; + int high_curr_cnt; + int init_cnt; + int v_to_cap; + bool recovery_needed; + bool high_curr_mode; + bool init_capacity; + enum ab5500_fg_calibration_state calib_state; + enum ab5500_fg_discharge_state discharge_state; + enum ab5500_fg_charge_state charge_state; + struct ab5500_fg_flags flags; + struct ab5500_fg_battery_capacity bat_cap; + struct ab5500_fg_avg_cap avg_cap; + struct ab5500 *parent; + struct ab5500_gpadc *gpadc; + struct adc_auto_input *gpadc_auto; + struct abx500_fg_platform_data *pdata; + struct abx500_bm_data *bat; + struct power_supply fg_psy; + struct workqueue_struct *fg_wq; + struct delayed_work fg_periodic_work; + struct delayed_work fg_low_bat_work; + struct work_struct fg_work; + struct delayed_work fg_acc_cur_work; + struct mutex cc_lock; + struct list_head node; + struct timer_list avg_current_timer; +}; + +/* Main battery properties */ +static enum power_supply_property ab5500_fg_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, +}; + +struct ab5500_fg *ab5500_fg_get(void) +{ + struct ab5500_fg *di; + di = list_first_entry(&ab5500_fg_list, struct ab5500_fg, node); + + return di; +} + +/** + * ab5500_fg_is_low_curr() - Low or high current mode + * @di: pointer to the ab5500_fg structure + * @curr: the current to base or our decision on + * + * Low current mode if the current consumption is below a certain threshold + */ +static int ab5500_fg_is_low_curr(struct ab5500_fg *di, int curr) +{ + /* + * We want to know if we're in low current mode + */ + if (curr > -di->bat->fg_params->high_curr_threshold) + return true; + else + return false; +} + +/** + * ab5500_fg_add_cap_sample() - Add capacity to average filter + * @di: pointer to the ab5500_fg structure + * @sample: the capacity in mAh to add to the filter + * + * A capacity is added to the filter and a new mean capacity is calculated and + * returned + */ +static int ab5500_fg_add_cap_sample(struct ab5500_fg *di, int sample) +{ + struct timespec ts; + struct ab5500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + do { + avg->sum += sample - avg->samples[avg->pos]; + avg->samples[avg->pos] = sample; + avg->time_stamps[avg->pos] = ts.tv_sec; + avg->pos++; + + if (avg->pos == NBR_AVG_SAMPLES) + avg->pos = 0; + + if (avg->nbr_samples < NBR_AVG_SAMPLES) + avg->nbr_samples++; + + /* + * Check the time stamp for each sample. If too old, + * replace with latest sample + */ + } while (ts.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]); + + avg->avg = avg->sum / avg->nbr_samples; + + return avg->avg; +} + +/** + * ab5500_fg_fill_cap_sample() - Fill average filter + * @di: pointer to the ab5500_fg structure + * @sample: the capacity in mAh to fill the filter with + * + * The capacity filter is filled with a capacity in mAh + */ +static void ab5500_fg_fill_cap_sample(struct ab5500_fg *di, int sample) +{ + int i; + struct timespec ts; + struct ab5500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + for (i = 0; i < NBR_AVG_SAMPLES; i++) { + avg->samples[i] = sample; + avg->time_stamps[i] = ts.tv_sec; + } + + avg->pos = 0; + avg->nbr_samples = NBR_AVG_SAMPLES; + avg->sum = sample * NBR_AVG_SAMPLES; + avg->avg = sample; +} + +/** + * ab5500_fg_coulomb_counter() - enable coulomb counter + * @di: pointer to the ab5500_fg structure + * @enable: enable/disable + * + * Enable/Disable coulomb counter. + * On failure returns negative value. + */ +static int ab5500_fg_coulomb_counter(struct ab5500_fg *di, bool enable) +{ + int ret = 0; + mutex_lock(&di->cc_lock); + if (enable) { + /* Power-up the CC */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A, + (FG_ON | FG_ACC_RESET_ON_READ)); + if (ret) + goto cc_err; + + di->flags.fg_enabled = true; + } else { + /* Stop the CC */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A, + FG_ON_MASK, RESET); + if (ret) + goto cc_err; + + di->flags.fg_enabled = false; + + } + dev_dbg(di->dev, " CC enabled: %d Samples: %d\n", + enable, di->fg_samples); + + mutex_unlock(&di->cc_lock); + + return ret; +cc_err: + dev_err(di->dev, "%s Enabling coulomb counter failed\n", __func__); + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab5500_fg_inst_curr() - battery instantaneous current + * @di: pointer to the ab5500_fg structure + * + * Returns battery instantenous current(on success) else error code + */ +static int ab5500_fg_inst_curr(struct ab5500_fg *di) +{ + u8 low, high; + static int val; + int ret = 0; + bool fg_off = false; + + if (!di->flags.fg_enabled) { + fg_off = true; + /* Power-up the CC */ + ab5500_fg_coulomb_counter(di, true); + msleep(250); + } + + mutex_lock(&di->cc_lock); + /* + * Since there is no interrupt for this, just wait for 250ms + * 250ms is one sample conversion time with 32.768 Khz RTC clock + */ + msleep(250); + + /* Enable read request */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_B, + EN_READOUT_MASK, EN_READOUT); + if (ret) + goto inst_curr_err; + + /* Read CC Sample conversion value Low and high */ + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FGDIR_READ0, &low); + if (ret < 0) + goto inst_curr_err; + + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FGDIR_READ1, &high); + if (ret < 0) + goto inst_curr_err; + + /* + * negative value for Discharging + * convert 2's compliment into decimal + */ + if (high & 0x10) + val = (low | (high << 8) | 0xFFFFE000); + else + val = (low | (high << 8)); + + /* + * Convert to unit value in mA + * R(FGSENSE) = 20 mOhm + * Scaling of LSB: This corresponds fro R(FGSENSE) to a current of + * I = Q/t = 192.7 uC * 4 Hz = 0.77mA + */ + val = (val * 770) / 1000; + + mutex_unlock(&di->cc_lock); + + if (fg_off) { + dev_dbg(di->dev, "%s Disable FG\n", __func__); + /* Power-off the CC */ + ab5500_fg_coulomb_counter(di, false); + } + + return val; + +inst_curr_err: + dev_err(di->dev, "%s Get instanst current failed\n", __func__); + mutex_unlock(&di->cc_lock); + return ret; +} + +static void ab5500_fg_acc_cur_timer_expired(unsigned long data) +{ + struct ab5500_fg *di = (struct ab5500_fg *) data; + dev_dbg(di->dev, "Avg current timer expired\n"); + + /* Trigger execution of the algorithm instantly */ + queue_delayed_work(di->fg_wq, &di->fg_acc_cur_work, 0); +} + +/** + * ab5500_fg_acc_cur_work() - average battery current + * @work: pointer to the work_struct structure + * + * Updated the average battery current obtained from the + * coulomb counter. + */ +static void ab5500_fg_acc_cur_work(struct work_struct *work) +{ + int val; + int ret; + u8 low, med, high, cnt_low, cnt_high; + + struct ab5500_fg *di = container_of(work, + struct ab5500_fg, fg_acc_cur_work.work); + + if (!di->flags.fg_enabled) { + /* Power-up the CC */ + ab5500_fg_coulomb_counter(di, true); + msleep(250); + } + mutex_lock(&di->cc_lock); + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_C, + EN_READOUT_MASK, EN_READOUT); + if (ret < 0) + goto exit; + /* If charging read charging registers for accumulated values */ + if (di->flags.charging) { + /* Read CC Sample conversion value Low and high */ + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_CH0, &low); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_CH1, &med); + if (ret < 0) + goto exit; + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_CH2, &high); + if (ret < 0) + goto exit; + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_VAL_COUNT0, &cnt_low); + if (ret < 0) + goto exit; + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_VAL_COUNT1, &cnt_high); + if (ret < 0) + goto exit; + queue_delayed_work(di->fg_wq, &di->fg_acc_cur_work, + di->bat->interval_charging * HZ); + } else { /* discharging */ + /* Read CC Sample conversion value Low and high */ + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_DIS_CH0, &low); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_DIS_CH1, &med); + if (ret < 0) + goto exit; + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_DIS_CH2, &high); + if (ret < 0) + goto exit; + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_VAL_COUNT0, &cnt_low); + if (ret < 0) + goto exit; + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_VAL_COUNT1, &cnt_high); + if (ret < 0) + goto exit; + queue_delayed_work(di->fg_wq, &di->fg_acc_cur_work, + di->bat->interval_not_charging * HZ); + } + di->fg_samples = (cnt_low | (cnt_high << 8)); + val = (low | (med << 8) | (high << 16)); + + if (di->fg_samples) { + di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10)/10000; + di->avg_curr = (val * FG_LSB_IN_MA) / (di->fg_samples * 1000); + } else + dev_err(di->dev, + "samples is zero, using previous calculated average current\n"); + di->flags.conv_done = true; + + mutex_unlock(&di->cc_lock); + + queue_work(di->fg_wq, &di->fg_work); + + return; +exit: + dev_err(di->dev, + "Failed to read or write gas gauge registers\n"); + mutex_unlock(&di->cc_lock); + queue_work(di->fg_wq, &di->fg_work); +} + +/** + * ab5500_fg_bat_voltage() - get battery voltage + * @di: pointer to the ab5500_fg structure + * + * Returns battery voltage(on success) else error code + */ +static int ab5500_fg_bat_voltage(struct ab5500_fg *di) +{ + int vbat; + static int prev; + + vbat = ab5500_gpadc_convert(di->gpadc, MAIN_BAT_V); + if (vbat < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value\n", + __func__); + return prev; + } + + prev = vbat; + return vbat; +} + +/** + * ab5500_fg_volt_to_capacity() - Voltage based capacity + * @di: pointer to the ab5500_fg structure + * @voltage: The voltage to convert to a capacity + * + * Returns battery capacity in per mille based on voltage + */ +static int ab5500_fg_volt_to_capacity(struct ab5500_fg *di, int voltage) +{ + int i, tbl_size; + struct abx500_v_to_cap *tbl; + int cap = 0; + + tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl, + tbl_size = di->bat->bat_type[di->bat->batt_id].n_v_cap_tbl_elements; + + for (i = 0; i < tbl_size; ++i) { + if (di->vbat < tbl[i].voltage && di->vbat > tbl[i+1].voltage) + di->v_to_cap = tbl[i].capacity; + } + + for (i = 0; i < tbl_size; ++i) { + if (voltage > tbl[i].voltage) + break; + } + + if ((i > 0) && (i < tbl_size)) { + cap = interpolate(voltage, + tbl[i].voltage, + tbl[i].capacity * 10, + tbl[i-1].voltage, + tbl[i-1].capacity * 10); + } else if (i == 0) { + cap = 1000; + } else { + cap = 0; + } + + dev_dbg(di->dev, "%s Vbat: %d, Cap: %d per mille", + __func__, voltage, cap); + + return cap; +} + +/** + * ab5500_fg_uncomp_volt_to_capacity() - Uncompensated voltage based capacity + * @di: pointer to the ab5500_fg structure + * + * Returns battery capacity based on battery voltage that is not compensated + * for the voltage drop due to the load + */ +static int ab5500_fg_uncomp_volt_to_capacity(struct ab5500_fg *di) +{ + di->vbat = ab5500_fg_bat_voltage(di); + return ab5500_fg_volt_to_capacity(di, di->vbat); +} + +/** + * ab5500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity + * @di: pointer to the ab5500_fg structure + * + * Returns battery capacity based on battery voltage that is load compensated + * for the voltage drop + */ +static int ab5500_fg_load_comp_volt_to_capacity(struct ab5500_fg *di) +{ + int vbat_comp; + + di->inst_curr = ab5500_fg_inst_curr(di); + di->vbat = ab5500_fg_bat_voltage(di); + + /* Use Ohms law to get the load compensated voltage */ + vbat_comp = di->vbat - (di->inst_curr * + di->bat->bat_type[di->bat->batt_id].battery_resistance) / 1000; + + dev_dbg(di->dev, "%s Measured Vbat: %dmV,Compensated Vbat %dmV, " + "R: %dmOhm, Current: %dmA\n", + __func__, + di->vbat, + vbat_comp, + di->bat->bat_type[di->bat->batt_id].battery_resistance, + di->inst_curr); + + return ab5500_fg_volt_to_capacity(di, vbat_comp); +} + +/** + * ab5500_fg_convert_mah_to_permille() - Capacity in mAh to permille + * @di: pointer to the ab5500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in permille + */ +static int ab5500_fg_convert_mah_to_permille(struct ab5500_fg *di, int cap_mah) +{ + return (cap_mah * 1000) / di->bat_cap.max_mah_design; +} + +/** + * ab5500_fg_convert_permille_to_mah() - Capacity in permille to mAh + * @di: pointer to the ab5500_fg structure + * @cap_pm: capacity in permille + * + * Converts capacity in permille to capacity in mAh + */ +static int ab5500_fg_convert_permille_to_mah(struct ab5500_fg *di, int cap_pm) +{ + return cap_pm * di->bat_cap.max_mah_design / 1000; +} + +/** + * ab5500_fg_convert_mah_to_uwh() - Capacity in mAh to uWh + * @di: pointer to the ab5500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in uWh + */ +static int ab5500_fg_convert_mah_to_uwh(struct ab5500_fg *di, int cap_mah) +{ + u64 div_res; + u32 div_rem; + + div_res = ((u64) cap_mah) * ((u64) di->vbat_nom); + div_rem = do_div(div_res, 1000); + + /* Make sure to round upwards if necessary */ + if (div_rem >= 1000 / 2) + div_res++; + + return (int) div_res; +} + +/** + * ab5500_fg_calc_cap_charging() - Calculate remaining capacity while charging + * @di: pointer to the ab5500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. The filter is filled with this capacity + */ +static int ab5500_fg_calc_cap_charging(struct ab5500_fg *di) +{ + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + + /* + * We force capacity to 100% as long as the algorithm + * reports that it's full. + */ + if (di->bat_cap.mah >= di->bat_cap.max_mah_design || + di->flags.fully_charged) + di->bat_cap.mah = di->bat_cap.max_mah_design; + + ab5500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab5500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + /* We need to update battery voltage and inst current when charging */ + di->vbat = ab5500_fg_bat_voltage(di); + di->inst_curr = ab5500_fg_inst_curr(di); + + return di->bat_cap.mah; +} + +/** + * ab5500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage + * @di: pointer to the ab5500_fg structure + * @comp: if voltage should be load compensated before capacity calc + * + * Return the capacity in mAh based on the battery voltage. The voltage can + * either be load compensated or not. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab5500_fg_calc_cap_discharge_voltage(struct ab5500_fg *di, bool comp) +{ + int permille, mah; + + if (comp) + permille = ab5500_fg_load_comp_volt_to_capacity(di); + else + permille = ab5500_fg_uncomp_volt_to_capacity(di); + + mah = ab5500_fg_convert_permille_to_mah(di, permille); + + di->bat_cap.mah = ab5500_fg_add_cap_sample(di, mah); + di->bat_cap.permille = + ab5500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + return di->bat_cap.mah; +} + +/** + * ab5500_fg_calc_cap_discharge_fg() - Capacity in discharge with FG + * @di: pointer to the ab5500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab5500_fg_calc_cap_discharge_fg(struct ab5500_fg *di) +{ + int permille_volt, permille; + + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + + if (di->bat_cap.mah >= di->bat_cap.max_mah_design) + di->bat_cap.mah = di->bat_cap.max_mah_design; + + /* + * Check against voltage based capacity. It can not be lower + * than what the uncompensated voltage says + */ + permille = ab5500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + permille_volt = ab5500_fg_uncomp_volt_to_capacity(di); + + if (permille < permille_volt) { + di->bat_cap.permille = permille_volt; + di->bat_cap.mah = ab5500_fg_convert_permille_to_mah(di, + di->bat_cap.permille); + + dev_dbg(di->dev, "%s voltage based: perm %d perm_volt %d\n", + __func__, + permille, + permille_volt); + + ab5500_fg_fill_cap_sample(di, di->bat_cap.mah); + } else { + ab5500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab5500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + } + + return di->bat_cap.mah; +} + +/** + * ab5500_fg_capacity_level() - Get the battery capacity level + * @di: pointer to the ab5500_fg structure + * + * Get the battery capacity level based on the capacity in percent + */ +static int ab5500_fg_capacity_level(struct ab5500_fg *di) +{ + int ret, percent; + + percent = di->bat_cap.permille / 10; + + if (percent <= di->bat->cap_levels->critical || + di->flags.low_bat) + ret = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else if (percent <= di->bat->cap_levels->low) + ret = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (percent <= di->bat->cap_levels->normal) + ret = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (percent <= di->bat->cap_levels->high) + ret = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + else + ret = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + + return ret; +} + +/** + * ab5500_fg_check_capacity_limits() - Check if capacity has changed + * @di: pointer to the ab5500_fg structure + * @init: capacity is allowed to go up in init mode + * + * Check if capacity or capacity limit has changed and notify the system + * about it using the power_supply framework + */ +static void ab5500_fg_check_capacity_limits(struct ab5500_fg *di, bool init) +{ + bool changed = false; + + di->bat_cap.level = ab5500_fg_capacity_level(di); + + if (di->bat_cap.level != di->bat_cap.prev_level) { + /* + * We do not allow reported capacity level to go up + * unless we're charging or if we're in init + */ + if (!(!di->flags.charging && di->bat_cap.level > + di->bat_cap.prev_level) || init) { + dev_dbg(di->dev, "level changed from %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + di->bat_cap.prev_level = di->bat_cap.level; + changed = true; + } else { + dev_dbg(di->dev, "level not allowed to go up " + "since no charger is connected: %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + } + } + + /* + * If we have received the LOW_BAT IRQ, set capacity to 0 to initiate + * shutdown + */ + if (di->flags.low_bat) { + dev_dbg(di->dev, "Battery low, set capacity to 0\n"); + di->bat_cap.prev_percent = 0; + di->bat_cap.permille = 0; + di->bat_cap.prev_mah = 0; + di->bat_cap.mah = 0; + changed = true; + } else if (di->bat_cap.prev_percent != di->bat_cap.permille / 10) { + if (di->bat_cap.permille / 10 == 0) { + /* + * We will not report 0% unless we've got + * the LOW_BAT IRQ, no matter what the FG + * algorithm says. + */ + di->bat_cap.prev_percent = 1; + di->bat_cap.permille = 1; + di->bat_cap.prev_mah = 1; + di->bat_cap.mah = 1; + + changed = true; + } else if (!(!di->flags.charging && + (di->bat_cap.permille / 10) > + di->bat_cap.prev_percent) || init) { + /* + * We do not allow reported capacity to go up + * unless we're charging or if we're in init + */ + dev_dbg(di->dev, + "capacity changed from %d to %d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.permille / 10, + di->bat_cap.permille); + di->bat_cap.prev_percent = di->bat_cap.permille / 10; + di->bat_cap.prev_mah = di->bat_cap.mah; + + changed = true; + } else { + dev_dbg(di->dev, "capacity not allowed to go up since " + "no charger is connected: %d to %d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.permille / 10, + di->bat_cap.permille); + } + } + + if (changed) + power_supply_changed(&di->fg_psy); + +} + +static void ab5500_fg_charge_state_to(struct ab5500_fg *di, + enum ab5500_fg_charge_state new_state) +{ + dev_dbg(di->dev, "Charge state from %d [%s] to %d [%s]\n", + di->charge_state, + charge_state[di->charge_state], + new_state, + charge_state[new_state]); + + di->charge_state = new_state; +} + +static void ab5500_fg_discharge_state_to(struct ab5500_fg *di, + enum ab5500_fg_charge_state new_state) +{ + dev_dbg(di->dev, "Disharge state from %d [%s] to %d [%s]\n", + di->discharge_state, + discharge_state[di->discharge_state], + new_state, + discharge_state[new_state]); + + di->discharge_state = new_state; +} + +/** + * ab5500_fg_algorithm_charging() - FG algorithm for when charging + * @di: pointer to the ab5500_fg structure + * + * Battery capacity calculation state machine for when we're charging + */ +static void ab5500_fg_algorithm_charging(struct ab5500_fg *di) +{ + /* + * If we change to discharge mode + * we should start with recovery + */ + if (di->discharge_state != AB5500_FG_DISCHARGE_INIT_RECOVERY) + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_INIT_RECOVERY); + + switch (di->charge_state) { + case AB5500_FG_CHARGE_INIT: + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_charging); + + ab5500_fg_coulomb_counter(di, true); + ab5500_fg_charge_state_to(di, AB5500_FG_CHARGE_READOUT); + + break; + + case AB5500_FG_CHARGE_READOUT: + /* + * Read the FG and calculate the new capacity + */ + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + ab5500_fg_calc_cap_charging(di); + + break; + + default: + break; + } + + /* Check capacity limits */ + ab5500_fg_check_capacity_limits(di, false); +} + +/** + * ab5500_fg_algorithm_discharging() - FG algorithm for when discharging + * @di: pointer to the ab5500_fg structure + * + * Battery capacity calculation state machine for when we're discharging + */ +static void ab5500_fg_algorithm_discharging(struct ab5500_fg *di) +{ + int sleep_time; + + /* If we change to charge mode we should start with init */ + if (di->charge_state != AB5500_FG_CHARGE_INIT) + ab5500_fg_charge_state_to(di, AB5500_FG_CHARGE_INIT); + + switch (di->discharge_state) { + case AB5500_FG_DISCHARGE_INIT: + /* We use the FG IRQ to work on */ + di->init_cnt = 0; + di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); + ab5500_fg_coulomb_counter(di, true); + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_INITMEASURING); + + /* Intentional fallthrough */ + case AB5500_FG_DISCHARGE_INITMEASURING: + /* + * Discard a number of samples during startup. + * After that, use compensated voltage for a few + * samples to get an initial capacity. + * Then go to READOUT + */ + sleep_time = di->bat->fg_params->init_timer; + + /* Discard the first [x] seconds */ + if (di->init_cnt > + di->bat->fg_params->init_discard_time) { + + ab5500_fg_calc_cap_discharge_voltage(di, true); + + ab5500_fg_check_capacity_limits(di, true); + } + + di->init_cnt += sleep_time; + if (di->init_cnt > + di->bat->fg_params->init_total_time) { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + + ab5500_fg_coulomb_counter(di, true); + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_READOUT); + } + + break; + + case AB5500_FG_DISCHARGE_INIT_RECOVERY: + di->recovery_cnt = 0; + di->recovery_needed = true; + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_RECOVERY); + + /* Intentional fallthrough */ + + case AB5500_FG_DISCHARGE_RECOVERY: + sleep_time = di->bat->fg_params->recovery_sleep_timer; + + /* + * We should check the power consumption + * If low, go to READOUT (after x min) or + * RECOVERY_SLEEP if time left. + * If high, go to READOUT + */ + di->inst_curr = ab5500_fg_inst_curr(di); + + if (ab5500_fg_is_low_curr(di, di->inst_curr)) { + if (di->recovery_cnt > + di->bat->fg_params->recovery_total_time) { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab5500_fg_coulomb_counter(di, true); + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_READOUT); + di->recovery_needed = false; + } else { + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, + sleep_time * HZ); + } + di->recovery_cnt += sleep_time; + } else { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab5500_fg_coulomb_counter(di, true); + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_READOUT); + } + + break; + + case AB5500_FG_DISCHARGE_READOUT: + di->inst_curr = ab5500_fg_inst_curr(di); + + if (ab5500_fg_is_low_curr(di, di->inst_curr)) { + /* Detect mode change */ + if (di->high_curr_mode) { + di->high_curr_mode = false; + di->high_curr_cnt = 0; + } + + if (di->recovery_needed) { + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_RECOVERY); + + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, + 0); + + break; + } + + ab5500_fg_calc_cap_discharge_voltage(di, true); + } else { + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + /* Detect mode change */ + if (!di->high_curr_mode) { + di->high_curr_mode = true; + di->high_curr_cnt = 0; + } + + di->high_curr_cnt += + di->bat->fg_params->accu_high_curr; + if (di->high_curr_cnt > + di->bat->fg_params->high_curr_time) + di->recovery_needed = true; + + ab5500_fg_calc_cap_discharge_fg(di); + } + + ab5500_fg_check_capacity_limits(di, false); + + break; + + case AB5500_FG_DISCHARGE_WAKEUP: + ab5500_fg_coulomb_counter(di, true); + di->inst_curr = ab5500_fg_inst_curr(di); + + ab5500_fg_calc_cap_discharge_voltage(di, true); + + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + /* Re-program number of samples set above */ + ab5500_fg_coulomb_counter(di, true); + ab5500_fg_discharge_state_to(di, AB5500_FG_DISCHARGE_READOUT); + + ab5500_fg_check_capacity_limits(di, false); + + break; + + default: + break; + } +} + +/** + * ab5500_fg_algorithm_calibrate() - Internal columb counter offset calibration + * @di: pointer to the ab5500_fg structure + * + */ +static void ab5500_fg_algorithm_calibrate(struct ab5500_fg *di) +{ + int ret; + + switch (di->calib_state) { + case AB5500_FG_CALIB_INIT: + dev_dbg(di->dev, "Calibration ongoing...\n"); + /* TODO: For Cut 1.1 no calibration */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A, + FG_ACC_RESET_ON_READ_MASK, FG_ACC_RESET_ON_READ); + if (ret) + goto err; + di->calib_state = AB5500_FG_CALIB_WAIT; + break; + case AB5500_FG_CALIB_END: + di->flags.calibrate = false; + dev_dbg(di->dev, "Calibration done...\n"); + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + break; + case AB5500_FG_CALIB_WAIT: + dev_dbg(di->dev, "Calibration WFI\n"); + default: + break; + } + return; +err: + /* Something went wrong, don't calibrate then */ + dev_err(di->dev, "failed to calibrate the CC\n"); + di->flags.calibrate = false; + di->calib_state = AB5500_FG_CALIB_INIT; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); +} + +/** + * ab5500_fg_algorithm() - Entry point for the FG algorithm + * @di: pointer to the ab5500_fg structure + * + * Entry point for the battery capacity calculation state machine + */ +static void ab5500_fg_algorithm(struct ab5500_fg *di) +{ + if (di->flags.calibrate) + ab5500_fg_algorithm_calibrate(di); + else { + if (di->flags.charging) + ab5500_fg_algorithm_charging(di); + else + ab5500_fg_algorithm_discharging(di); + } + + dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d " + "%d %d %d %d %d %d %d\n", + di->bat_cap.max_mah_design, + di->bat_cap.mah, + di->bat_cap.permille, + di->bat_cap.level, + di->bat_cap.prev_mah, + di->bat_cap.prev_percent, + di->bat_cap.prev_level, + di->vbat, + di->inst_curr, + di->avg_curr, + di->accu_charge, + di->flags.charging, + di->charge_state, + di->discharge_state, + di->high_curr_mode, + di->recovery_needed); +} + +/** + * ab5500_fg_periodic_work() - Run the FG state machine periodically + * @work: pointer to the work_struct structure + * + * Work queue function for periodic work + */ +static void ab5500_fg_periodic_work(struct work_struct *work) +{ + struct ab5500_fg *di = container_of(work, struct ab5500_fg, + fg_periodic_work.work); + + if (di->init_capacity) { + /* A dummy read that will return 0 */ + di->inst_curr = ab5500_fg_inst_curr(di); + /* Get an initial capacity calculation */ + ab5500_fg_calc_cap_discharge_voltage(di, true); + ab5500_fg_check_capacity_limits(di, true); + di->init_capacity = false; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + } else + ab5500_fg_algorithm(di); +} + +/** + * ab5500_fg_low_bat_work() - Check LOW_BAT condition + * @work: pointer to the work_struct structure + * + * Work queue function for checking the LOW_BAT condition + */ +static void ab5500_fg_low_bat_work(struct work_struct *work) +{ + int vbat; + + struct ab5500_fg *di = container_of(work, struct ab5500_fg, + fg_low_bat_work.work); + + vbat = ab5500_fg_bat_voltage(di); + + /* Check if LOW_BAT still fulfilled */ + if (vbat < di->bat->fg_params->lowbat_threshold) { + di->flags.low_bat = true; + dev_warn(di->dev, "Battery voltage still LOW\n"); + + /* + * We need to re-schedule this check to be able to detect + * if the voltage increases again during charging + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } else { + di->flags.low_bat = false; + dev_warn(di->dev, "Battery voltage OK again\n"); + } + + /* This is needed to dispatch LOW_BAT */ + ab5500_fg_check_capacity_limits(di, false); + + /* Set this flag to check if LOW_BAT IRQ still occurs */ + di->flags.low_bat_delay = false; +} + +/** + * ab5500_fg_instant_work() - Run the FG state machine instantly + * @work: pointer to the work_struct structure + * + * Work queue function for instant work + */ +static void ab5500_fg_instant_work(struct work_struct *work) +{ + struct ab5500_fg *di = container_of(work, struct ab5500_fg, fg_work); + + ab5500_fg_algorithm(di); +} + +/** + * ab5500_fg_get_property() - get the fg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * fg properties by reading the sysfs files. + * voltage_now: battery voltage + * current_now: battery instant current + * current_avg: battery average current + * charge_full_design: capacity where battery is considered full + * charge_now: battery capacity in nAh + * capacity: capacity in percent + * capacity_level: capacity level + * + * Returns error code in case of failure else 0 on success + */ +static int ab5500_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab5500_fg *di; + int i, tbl_size; + struct abx500_v_to_cap *tbl; + + di = to_ab5500_fg_device_info(psy); + + /* + * If battery is identified as unknown and charging of unknown + * batteries is disabled, we always report 100% capacity and + * capacity level UNKNOWN, since we can't calculate + * remaining capacity + */ + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (di->flags.bat_ovv) + val->intval = 47500000; + else { + di->vbat = ab5500_gpadc_convert + (di->gpadc, MAIN_BAT_V); + val->intval = di->vbat * 1000; + } + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + di->inst_curr = ab5500_fg_inst_curr(di); + val->intval = di->inst_curr * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = di->avg_curr * 1000; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = ab5500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah_design); + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + val->intval = ab5500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = ab5500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + else + val->intval = ab5500_fg_convert_mah_to_uwh(di, + di->bat_cap.prev_mah); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = di->bat_cap.max_mah_design; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = di->bat_cap.max_mah; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = di->bat_cap.max_mah; + else + val->intval = di->bat_cap.prev_mah; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = 100; + else if (di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl) { + tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl, + tbl_size = di->bat->bat_type[ + di->bat->batt_id].n_v_cap_tbl_elements; + + for (i = 0; i < tbl_size; ++i) { + if (di->vbat < tbl[i].voltage && + di->vbat > tbl[i+1].voltage) { + di->v_to_cap = tbl[i].capacity; + break; + } + } + val->intval = di->v_to_cap; + } else + val->intval = di->bat_cap.prev_percent; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + else + val->intval = di->bat_cap.prev_level; + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab5500_fg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab5500_fg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab5500_fg_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + switch (ret.intval) { + case POWER_SUPPLY_STATUS_UNKNOWN: + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (!di->flags.charging) + break; + di->flags.charging = false; + di->flags.fully_charged = false; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_FULL: + if (di->flags.fully_charged) + break; + di->flags.fully_charged = true; + /* Save current capacity as maximum */ + di->bat_cap.max_mah = di->bat_cap.mah; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (di->flags.charging) + break; + di->flags.charging = true; + di->flags.fully_charged = false; + queue_work(di->fg_wq, &di->fg_work); + break; + }; + default: + break; + }; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (ret.intval) + di->flags.batt_unknown = false; + else + di->flags.batt_unknown = true; + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +static int ab5500_fg_bat_v_trig(int mux) +{ + struct ab5500_fg *di = ab5500_fg_get(); + + /* check if the battery voltage is below low threshold */ + if (di->vbat < 2700) { + dev_warn(di->dev, "Battery voltage is below LOW threshold\n"); + di->flags.low_bat_delay = true; + /* + * Start a timer to check LOW_BAT again after some time + * This is done to avoid shutdown on single voltage dips + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } + /* check if battery votlage is above OVV */ + else if (di->vbat > 4200) { + dev_dbg(di->dev, "Battery OVV\n"); + di->flags.bat_ovv = true; + + power_supply_changed(&di->fg_psy); + } else + return -EINVAL; + + return 0; +} + +/** + * ab5500_fg_init_hw_registers() - Set up FG related registers + * @di: pointer to the ab5500_fg structure + * + * Set up battery OVV, low battery voltage registers + */ +static int ab5500_fg_init_hw_registers(struct ab5500_fg *di) +{ + int ret; + struct adc_auto_input *auto_ip; + + auto_ip = kzalloc(sizeof(struct adc_auto_input), GFP_KERNEL); + if (!auto_ip) { + dev_err(di->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + auto_ip->mux = MAIN_BAT_V; + auto_ip->freq = MS500; + auto_ip->min = 2700; + auto_ip->max = 4200; + auto_ip->auto_adc_callback = ab5500_fg_bat_v_trig; + di->gpadc_auto = auto_ip; + ret = ab5500_gpadc_convert_auto(di->gpadc, di->gpadc_auto); + if (ret) + dev_err(di->dev, + "failed to set auto trigger for battery votlage\n"); + /* set End Of Charge current to 247mA */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_EOC, EOC_52_mA); + return ret; +} + +/** + * ab5500_fg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void ab5500_fg_external_power_changed(struct power_supply *psy) +{ + struct ab5500_fg *di = to_ab5500_fg_device_info(psy); + + class_for_each_device(power_supply_class, NULL, + &di->fg_psy, ab5500_fg_get_ext_psy_data); +} + +#if defined(CONFIG_PM) +static int ab5500_fg_resume(struct platform_device *pdev) +{ + struct ab5500_fg *di = platform_get_drvdata(pdev); + + /* + * Change state if we're not charging. If we're charging we will wake + * up on the FG IRQ + */ + if (!di->flags.charging) { + ab5500_fg_discharge_state_to(di, AB5500_FG_DISCHARGE_WAKEUP); + queue_work(di->fg_wq, &di->fg_work); + } + + return 0; +} + +static int ab5500_fg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab5500_fg *di = platform_get_drvdata(pdev); + + flush_delayed_work(&di->fg_periodic_work); + + /* + * If the FG is enabled we will disable it before going to suspend + * only if we're not charging + */ + if (di->flags.fg_enabled && !di->flags.charging) + ab5500_fg_coulomb_counter(di, false); + + return 0; +} +#else +#define ab5500_fg_suspend NULL +#define ab5500_fg_resume NULL +#endif + +static int __devexit ab5500_fg_remove(struct platform_device *pdev) +{ + int ret = 0; + struct ab5500_fg *di = platform_get_drvdata(pdev); + + /* Disable coulomb counter */ + ret = ab5500_fg_coulomb_counter(di, false); + if (ret) + dev_err(di->dev, "failed to disable coulomb counter\n"); + + destroy_workqueue(di->fg_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->fg_psy); + platform_set_drvdata(pdev, NULL); + kfree(di->gpadc_auto); + kfree(di); + return ret; +} + +static int __devinit ab5500_fg_probe(struct platform_device *pdev) +{ + struct abx500_bm_plat_data *plat_data; + int ret = 0; + + struct ab5500_fg *di = + kzalloc(sizeof(struct ab5500_fg), GFP_KERNEL); + if (!di) + return -ENOMEM; + + mutex_init(&di->cc_lock); + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab5500_gpadc_get("ab5500-adc.0"); + + plat_data = pdev->dev.platform_data; + di->pdata = plat_data->fg; + di->bat = plat_data->battery; + + /* get fg specific platform data */ + if (!di->pdata) { + dev_err(di->dev, "no fg platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + /* get battery specific platform data */ + if (!di->bat) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + di->fg_psy.name = "ab5500_fg"; + di->fg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->fg_psy.properties = ab5500_fg_props; + di->fg_psy.num_properties = ARRAY_SIZE(ab5500_fg_props); + di->fg_psy.get_property = ab5500_fg_get_property; + di->fg_psy.supplied_to = di->pdata->supplied_to; + di->fg_psy.num_supplicants = di->pdata->num_supplicants; + di->fg_psy.external_power_changed = ab5500_fg_external_power_changed; + + di->bat_cap.max_mah_design = MILLI_TO_MICRO * + di->bat->bat_type[di->bat->batt_id].charge_full_design; + + di->bat_cap.max_mah = di->bat_cap.max_mah_design; + + di->vbat_nom = di->bat->bat_type[di->bat->batt_id].nominal_voltage; + + di->init_capacity = true; + + ab5500_fg_charge_state_to(di, AB5500_FG_CHARGE_INIT); + ab5500_fg_discharge_state_to(di, AB5500_FG_DISCHARGE_INIT); + + /* Create a work queue for running the FG algorithm */ + di->fg_wq = create_singlethread_workqueue("ab5500_fg_wq"); + if (di->fg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for running the fg algorithm instantly */ + INIT_WORK(&di->fg_work, ab5500_fg_instant_work); + + /* Init work for getting the battery accumulated current */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_acc_cur_work, + ab5500_fg_acc_cur_work); + + /* Work delayed Queue to run the state machine */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_periodic_work, + ab5500_fg_periodic_work); + + /* Work to check low battery condition */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_low_bat_work, + ab5500_fg_low_bat_work); + + list_add_tail(&di->node, &ab5500_fg_list); + + /* Consider battery unknown until we're informed otherwise */ + di->flags.batt_unknown = true; + + /* Register FG power supply class */ + ret = power_supply_register(di->dev, &di->fg_psy); + if (ret) { + dev_err(di->dev, "failed to register FG psy\n"); + goto free_fg_wq; + } + + /* Initialize OVV, and other registers */ + ret = ab5500_fg_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize registers\n"); + goto pow_unreg; + } + + di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); + ab5500_fg_coulomb_counter(di, true); + + /* Initilialize avg current timer */ + init_timer(&di->avg_current_timer); + di->avg_current_timer.function = ab5500_fg_acc_cur_timer_expired; + di->avg_current_timer.data = (unsigned long) di; + di->avg_current_timer.expires = 60 * HZ;; + if (!timer_pending(&di->avg_current_timer)) + add_timer(&di->avg_current_timer); + else + mod_timer(&di->avg_current_timer, 60 * HZ); + + platform_set_drvdata(pdev, di); + + /* Calibrate the fg first time */ + di->flags.calibrate = true; + di->calib_state = AB5500_FG_CALIB_INIT; + /* Run the FG algorithm */ + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + queue_delayed_work(di->fg_wq, &di->fg_acc_cur_work, 0); + + dev_info(di->dev, "probe success\n"); + return ret; + +pow_unreg: + power_supply_unregister(&di->fg_psy); +free_fg_wq: + destroy_workqueue(di->fg_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab5500_fg_driver = { + .probe = ab5500_fg_probe, + .remove = __devexit_p(ab5500_fg_remove), + .suspend = ab5500_fg_suspend, + .resume = ab5500_fg_resume, + .driver = { + .name = "ab5500-fg", + .owner = THIS_MODULE, + }, +}; + +static int __init ab5500_fg_init(void) +{ + return platform_driver_register(&ab5500_fg_driver); +} + +static void __exit ab5500_fg_exit(void) +{ + platform_driver_unregister(&ab5500_fg_driver); +} + +subsys_initcall_sync(ab5500_fg_init); +module_exit(ab5500_fg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab5500-fg"); +MODULE_DESCRIPTION("AB5500 Fuel Gauge driver"); diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c new file mode 100644 index 00000000000..70e3a4688c9 --- /dev/null +++ b/drivers/power/ab8500_btemp.c @@ -0,0 +1,1126 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Battery temperature driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: Johan Palsson <johan.palsson@stericsson.com> + * Author: Karl Komierowski <karl.komierowski@stericsson.com> + * Author: Arun R Murthy <arun.murthy@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/mfd/ab8500.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/ab8500/bm.h> +#include <linux/mfd/ab8500/gpadc.h> + +#define VTVOUT_V 1800 + +#define BTEMP_THERMAL_LOW_LIMIT -10 +#define BTEMP_THERMAL_MED_LIMIT 0 +#define BTEMP_THERMAL_HIGH_LIMIT_52 52 +#define BTEMP_THERMAL_HIGH_LIMIT_57 57 +#define BTEMP_THERMAL_HIGH_LIMIT_62 62 + +#define BTEMP_BATCTRL_CURR_SRC_7UA 7 +#define BTEMP_BATCTRL_CURR_SRC_20UA 20 + +#define to_ab8500_btemp_device_info(x) container_of((x), \ + struct ab8500_btemp, btemp_psy); + +/** + * struct ab8500_btemp_interrupts - ab8500 interrupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_btemp_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab8500_btemp_events { + bool batt_rem; + bool btemp_high; + bool btemp_medhigh; + bool btemp_lowmed; + bool btemp_low; + bool ac_conn; + bool usb_conn; +}; + +struct ab8500_btemp_ranges { + int btemp_high_limit; + int btemp_med_limit; + int btemp_low_limit; +}; + +/** + * struct ab8500_btemp - ab8500 BTEMP device information + * @dev: Pointer to the structure device + * @node: List of AB8500 BTEMPs, hence prepared for reentrance + * @chip_id: Chip-Id of the AB8500 + * @curr_source: What current source we use, in uA + * @bat_temp: Battery temperature in degree Celcius + * @prev_bat_temp Last dispatched battery temperature + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @fg: Pointer to the struct fg + * @pdata: Pointer to the ab8500_btemp platform data + * @bat: Pointer to the ab8500_bm platform data + * @btemp_psy: Structure for BTEMP specific battery properties + * @events: Structure for information about events triggered + * @btemp_ranges: Battery temperature range structure + * @btemp_wq: Work queue for measuring the temperature periodically + * @btemp_periodic_work: Work for measuring the temperature periodically + */ +struct ab8500_btemp { + struct device *dev; + struct list_head node; + u8 chip_id; + int curr_source; + int bat_temp; + int prev_bat_temp; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct ab8500_fg *fg; + struct ab8500_btemp_platform_data *pdata; + struct ab8500_bm_data *bat; + struct power_supply btemp_psy; + struct ab8500_btemp_events events; + struct ab8500_btemp_ranges btemp_ranges; + struct workqueue_struct *btemp_wq; + struct delayed_work btemp_periodic_work; +}; + +/* BTEMP power supply properties */ +static enum power_supply_property ab8500_btemp_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_TEMP, +}; + +static LIST_HEAD(ab8500_btemp_list); + +/** + * ab8500_btemp_get() - returns a reference to the primary AB8500 BTEMP + * (i.e. the first BTEMP in the instance list) + */ +struct ab8500_btemp *ab8500_btemp_get(void) +{ + struct ab8500_btemp *btemp; + btemp = list_first_entry(&ab8500_btemp_list, struct ab8500_btemp, node); + + return btemp; +} + +/** + * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance + * @di: pointer to the ab8500_btemp structure + * @v_batctrl: measured batctrl voltage + * @inst_curr: measured instant current + * + * This function returns the battery resistance that is + * derived from the BATCTRL voltage. + * Returns value in Ohms. + */ +static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di, + int v_batctrl, int inst_curr) +{ + int rbs; + + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + /* + * For ABB cut1.0 and 1.1 BAT_CTRL is internally + * connected to 1.8V through a 450k resistor + */ + rbs = (450000 * (v_batctrl)) / (1800 - v_batctrl); + break; + default: + if (di->bat->adc_therm == ADC_THERM_BATCTRL) { + /* + * If the battery has internal NTC, we use the current + * source to calculate the resistance, 7uA or 20uA + */ + rbs = (v_batctrl * 1000 + - di->bat->gnd_lift_resistance * inst_curr) + / di->curr_source; + } else { + /* + * BAT_CTRL is internally + * connected to 1.8V through a 80k resistor + */ + rbs = (80000 * (v_batctrl)) / (1800 - v_batctrl); + } + break; + } + + return rbs; +} + +/** + * ab8500_btemp_read_batctrl_voltage() - measure batctrl voltage + * @di: pointer to the ab8500_btemp structure + * + * This function returns the voltage on BATCTRL. Returns value in mV. + */ +static int ab8500_btemp_read_batctrl_voltage(struct ab8500_btemp *di) +{ + int vbtemp; + static int prev; + + vbtemp = ab8500_gpadc_convert(di->gpadc, BAT_CTRL); + if (vbtemp < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value", + __func__); + return prev; + } + prev = vbtemp; + return vbtemp; +} + +/** + * ab8500_btemp_curr_source_enable() - enable/disable batctrl current source + * @di: pointer to the ab8500_btemp structure + * @enable: enable or disable the current source + * + * Enable or disable the current sources for the BatCtrl AD channel + */ +static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di, + bool enable) +{ + int curr; + int ret = 0; + + /* + * BATCTRL current sources are included on AB8500 cut2.0 + * and future versions + */ + if (di->chip_id == AB8500_CUT1P0 || di->chip_id == AB8500_CUT1P1) + return 0; + + /* Only do this for batteries with internal NTC */ + if (di->bat->adc_therm == ADC_THERM_BATCTRL && enable) { + if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA) + curr = BAT_CTRL_7U_ENA; + else + curr = BAT_CTRL_20U_ENA; + + dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed setting cmp_force\n", + __func__); + return ret; + } + + /* + * We have to wait one 32kHz cycle before enabling + * the current source, since ForceBatCtrlCmpHigh needs + * to be written in a separate cycle + */ + udelay(32); + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH | curr); + if (ret) { + dev_err(di->dev, "%s failed enabling current source\n", + __func__); + goto disable_curr_source; + } + } else if (di->bat->adc_therm == ADC_THERM_BATCTRL && !enable) { + dev_dbg(di->dev, "Disable BATCTRL curr source\n"); + + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, + ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + goto disable_curr_source; + } + + /* Enable Pull-Up and comparator */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); + if (ret) { + dev_err(di->dev, "%s failed enabling PU and comp\n", + __func__); + goto enable_pu_comp; + } + + /* + * We have to wait one 32kHz cycle before disabling + * ForceBatCtrlCmpHigh since this needs to be written + * in a separate cycle + */ + udelay(32); + + /* Disable 'force comparator' */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed disabling force comp\n", + __func__); + goto disable_force_comp; + } + } + return ret; + + /* + * We have to try unsetting FORCE_BAT_CTRL_CMP_HIGH one more time + * if we got an error above + */ +disable_curr_source: + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, + ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + return ret; + } +enable_pu_comp: + /* Enable Pull-Up and comparator */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); + if (ret) { + dev_err(di->dev, "%s failed enabling PU and comp\n", + __func__); + return ret; + } + +disable_force_comp: + /* + * We have to wait one 32kHz cycle before disabling + * ForceBatCtrlCmpHigh since this needs to be written + * in a separate cycle + */ + udelay(32); + + /* Disable 'force comparator' */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed disabling force comp\n", + __func__); + return ret; + } + + return ret; +} + +/** + * ab8500_btemp_get_batctrl_res() - get battery resistance + * @di: pointer to the ab8500_btemp structure + * + * This function returns the battery pack identification resistance. + * Returns value in Ohms. + */ +static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) +{ + int ret; + int batctrl = 0; + int res; + int inst_curr; + int i = 0; + + /* + * BATCTRL current sources are included on AB8500 cut2.0 + * and future versions + */ + ret = ab8500_btemp_curr_source_enable(di, true); + if (ret) { + dev_err(di->dev, "%s curr source enabled failed\n", __func__); + return ret; + } + + if (!di->fg) + di->fg = ab8500_fg_get(); + if (!di->fg || ab8500_fg_inst_curr_nonblocking(di->fg, &inst_curr)) + inst_curr = 0; + do { + batctrl += ab8500_btemp_read_batctrl_voltage(di); + i++; + msleep(1); + barrier(); + } while (inst_curr == INVALID_CURRENT); + batctrl /= i; + res = ab8500_btemp_batctrl_volt_to_res(di, batctrl, inst_curr); + + ret = ab8500_btemp_curr_source_enable(di, false); + if (ret) { + dev_err(di->dev, "%s curr source disable failed\n", __func__); + return ret; + } + + dev_dbg(di->dev, "%s batctrl: %d res: %d inst_curr: %d\n", + __func__, batctrl, res, inst_curr); + + return res; +} + +/** + * ab8500_btemp_res_to_temp() - resistance to temperature + * @di: pointer to the ab8500_btemp structure + * @tbl: pointer to the resiatance to temperature table + * @tbl_size: size of the resistance to temperature table + * @res: resistance to calculate the temperature from + * + * This function returns the battery temperature in degrees Celcius + * based on the NTC resistance. + */ +static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di, + const struct res_to_temp *tbl, int tbl_size, int res) +{ + int i, temp; + /* + * Calculate the formula for the straight line + * Simple interpolation if we are within + * the resistance table limits, extrapolate + * if resistance is outside the limits. + */ + if (res > tbl[0].resist) + i = 0; + else if (res <= tbl[tbl_size - 1].resist) + i = tbl_size - 2; + else { + i = 0; + while (!(res <= tbl[i].resist && + res > tbl[i + 1].resist)) + i++; + } + + temp = tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) * + (res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); + return temp; +} + +/** + * ab8500_btemp_measure_temp() - measure battery temperature + * @di: pointer to the ab8500_btemp structure + * + * Returns battery temperature (on success) else the previous temperature + */ +static int ab8500_btemp_measure_temp(struct ab8500_btemp *di) +{ + int temp; + static int prev; + int rbat, rntc, vntc; + u8 id; + + id = di->bat->batt_id; + + if (di->bat->adc_therm == ADC_THERM_BATCTRL && + id != BATTERY_UNKNOWN) { + + rbat = ab8500_btemp_get_batctrl_res(di); + if (rbat < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", + __func__); + /* + * Return out-of-range temperature so that + * charging is stopped + */ + return BTEMP_THERMAL_LOW_LIMIT; + } + + temp = ab8500_btemp_res_to_temp(di, + di->bat->bat_type[id].r_to_t_tbl, + di->bat->bat_type[id].n_temp_tbl_elements, rbat); + } else { + vntc = ab8500_gpadc_convert(di->gpadc, BTEMP_BALL); + if (vntc < 0) { + dev_err(di->dev, + "%s gpadc conversion failed," + " using previous value\n", __func__); + return prev; + } + /* + * The PCB NTC is sourced from VTVOUT via a 230kOhm + * resistor. + */ + rntc = 230000 * vntc / (VTVOUT_V - vntc); + + temp = ab8500_btemp_res_to_temp(di, + di->bat->bat_type[id].r_to_t_tbl, + di->bat->bat_type[id].n_temp_tbl_elements, rntc); + prev = temp; + } + dev_dbg(di->dev, "Battery temperature is %d\n", temp); + return temp; +} + +/** + * ab8500_btemp_id() - Identify the connected battery + * @di: pointer to the ab8500_btemp structure + * + * This function will try to identify the battery by reading the ID + * resistor. Some brands use a combined ID resistor with a NTC resistor to + * both be able to identify and to read the temperature of it. + */ +static int ab8500_btemp_id(struct ab8500_btemp *di) +{ + int res; + u8 i; + + di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA; + di->bat->batt_id = BATTERY_UNKNOWN; + + res = ab8500_btemp_get_batctrl_res(di); + if (res < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", __func__); + return -ENXIO; + } + + /* BATTERY_UNKNOWN is defined on position 0, skip it! */ + for (i = BATTERY_UNKNOWN + 1; i < di->bat->n_btypes; i++) { + if ((res <= di->bat->bat_type[i].resis_high) && + (res >= di->bat->bat_type[i].resis_low)) { + dev_dbg(di->dev, "Battery detected on %s" + " low %d < res %d < high: %d" + " index: %d\n", + di->bat->adc_therm == ADC_THERM_BATCTRL ? + "BATCTRL" : "BATTEMP", + di->bat->bat_type[i].resis_low, res, + di->bat->bat_type[i].resis_high, i); + + di->bat->batt_id = i; + break; + } + } + + if (di->bat->batt_id == BATTERY_UNKNOWN) { + dev_warn(di->dev, "Battery identified as unknown" + ", resistance %d Ohm\n", res); + return -ENXIO; + } + + /* + * We only have to change current source if the + * detected type is Type 1, else we use the 7uA source + */ + if (di->bat->adc_therm == ADC_THERM_BATCTRL && di->bat->batt_id == 1) { + dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n"); + di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA; + } + + return di->bat->batt_id; +} + +/** + * ab8500_btemp_periodic_work() - Measuring the temperature periodically + * @work: pointer to the work_struct structure + * + * Work function for measuring the temperature periodically + */ +static void ab8500_btemp_periodic_work(struct work_struct *work) +{ + int interval; + struct ab8500_btemp *di = container_of(work, + struct ab8500_btemp, btemp_periodic_work.work); + + di->bat_temp = ab8500_btemp_measure_temp(di); + + if (di->bat_temp != di->prev_bat_temp) { + di->prev_bat_temp = di->bat_temp; + power_supply_changed(&di->btemp_psy); + } + + if (di->events.ac_conn || di->events.usb_conn) + interval = di->bat->temp_interval_chg; + else + interval = di->bat->temp_interval_nochg; + + /* Schedule a new measurement */ + queue_delayed_work(di->btemp_wq, + &di->btemp_periodic_work, + round_jiffies(interval * HZ)); +} + +/** + * ab8500_btemp_batctrlindb_handler() - battery removal detected + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_batctrlindb_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + dev_err(di->dev, "Battery removal detected!\n"); + + di->events.batt_rem = true; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_templow_handler() - battery temp lower than 10 degrees + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_templow_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + case AB8500_CUT2P0: + dev_dbg(di->dev, "Ignore false btemp low irq" + " for ABB cut 1.0, 1.1 and 2.0\n"); + + break; + default: + dev_crit(di->dev, "Battery temperature lower than -10deg c\n"); + + di->events.btemp_low = true; + di->events.btemp_high = false; + di->events.btemp_medhigh = false; + di->events.btemp_lowmed = false; + power_supply_changed(&di->btemp_psy); + + break; + } + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_temphigh_handler() - battery temp higher than max temp + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_temphigh_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_crit(di->dev, "Battery temperature is higher than MAX temp\n"); + + di->events.btemp_high = true; + di->events.btemp_medhigh = false; + di->events.btemp_lowmed = false; + di->events.btemp_low = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_lowmed_handler() - battery temp between low and medium + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_lowmed_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_dbg(di->dev, "Battery temperature is between low and medium\n"); + + di->events.btemp_lowmed = true; + di->events.btemp_medhigh = false; + di->events.btemp_high = false; + di->events.btemp_low = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_medhigh_handler() - battery temp between medium and high + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_medhigh_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_dbg(di->dev, "Battery temperature is between medium and high\n"); + + di->events.btemp_medhigh = true; + di->events.btemp_lowmed = false; + di->events.btemp_high = false; + di->events.btemp_low = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_periodic() - Periodic temperature measurements + * @di: pointer to the ab8500_btemp structure + * @enable: enable or disable periodic temperature measurements + * + * Starts of stops periodic temperature measurements. Periodic measurements + * should only be done when a charger is connected. + */ +static void ab8500_btemp_periodic(struct ab8500_btemp *di, + bool enable) +{ + dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n", + enable); + /* + * Make sure a new measurement is done directly by cancelling + * any pending work + */ + cancel_delayed_work_sync(&di->btemp_periodic_work); + + if (enable) + queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0); +} + +/** + * ab8500_btemp_get_temp() - get battery temperature + * @di: pointer to the ab8500_btemp structure + * + * Returns battery temperature + */ +static int ab8500_btemp_get_temp(struct ab8500_btemp *di) +{ + int temp = 0; + + /* + * The BTEMP events are not reliabe on AB8500 cut2.0 + * and prior versions + */ + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + case AB8500_CUT2P0: + temp = di->bat_temp * 10; + + break; + default: + if (di->events.btemp_low) { + if (temp > di->btemp_ranges.btemp_low_limit) + temp = di->btemp_ranges.btemp_low_limit; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_high) { + if (temp < di->btemp_ranges.btemp_high_limit) + temp = di->btemp_ranges.btemp_high_limit; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_lowmed) { + if (temp > di->btemp_ranges.btemp_med_limit) + temp = di->btemp_ranges.btemp_med_limit; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_medhigh) { + if (temp < di->btemp_ranges.btemp_med_limit) + temp = di->btemp_ranges.btemp_med_limit; + else + temp = di->bat_temp * 10; + } else + temp = di->bat_temp * 10; + + break; + } + return temp; +} + +/** + * ab8500_btemp_get_batctrl_temp() - get the temperature + * @btemp: pointer to the btemp structure + * + * Returns the batctrl temperature in millidegrees + */ +int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp) +{ + return btemp->bat_temp * 1000; +} + +/** + * ab8500_btemp_get_property() - get the btemp properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the btemp + * properties by reading the sysfs files. + * online: presence of the battery + * present: presence of the battery + * technology: battery technology + * temp: battery temperature + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_btemp_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_btemp *di; + + di = to_ab8500_btemp_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + if (di->events.batt_rem) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = di->bat->bat_type[di->bat->batt_id].name; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = ab8500_btemp_get_temp(di); + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_btemp *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_btemp_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + /* AC disconnected */ + if (!ret.intval && di->events.ac_conn) { + di->events.ac_conn = false; + } + /* AC connected */ + else if (ret.intval && !di->events.ac_conn) { + di->events.ac_conn = true; + if (!di->events.usb_conn) + ab8500_btemp_periodic(di, true); + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && di->events.usb_conn) { + di->events.usb_conn = false; + } + /* USB connected */ + else if (ret.intval && !di->events.usb_conn) { + di->events.usb_conn = true; + if (!di->events.ac_conn) + ab8500_btemp_periodic(di, true); + } + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_btemp_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is pointing to the function pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in the external power + * supply to the btemp. + */ +static void ab8500_btemp_external_power_changed(struct power_supply *psy) +{ + struct ab8500_btemp *di = to_ab8500_btemp_device_info(psy); + + class_for_each_device(power_supply_class, NULL, + &di->btemp_psy, ab8500_btemp_get_ext_psy_data); +} + +/* ab8500 btemp driver interrupts and their respective isr */ +static struct ab8500_btemp_interrupts ab8500_btemp_irq[] = { + {"BAT_CTRL_INDB", ab8500_btemp_batctrlindb_handler}, + {"BTEMP_LOW", ab8500_btemp_templow_handler}, + {"BTEMP_HIGH", ab8500_btemp_temphigh_handler}, + {"BTEMP_LOW_MEDIUM", ab8500_btemp_lowmed_handler}, + {"BTEMP_MEDIUM_HIGH", ab8500_btemp_medhigh_handler}, +}; + +#if defined(CONFIG_PM) +static int ab8500_btemp_resume(struct platform_device *pdev) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + + ab8500_btemp_periodic(di, true); + + return 0; +} + +static int ab8500_btemp_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + + ab8500_btemp_periodic(di, false); + + return 0; +} +#else +#define ab8500_btemp_suspend NULL +#define ab8500_btemp_resume NULL +#endif + +static int __devexit ab8500_btemp_remove(struct platform_device *pdev) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + int i, irq; + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + free_irq(irq, di); + } + + /* Delete the work queue */ + destroy_workqueue(di->btemp_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->btemp_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab8500_btemp_probe(struct platform_device *pdev) +{ + int irq, i, ret = 0; + u8 val; + struct ab8500_platform_data *plat; + + struct ab8500_btemp *di = + kzalloc(sizeof(struct ab8500_btemp), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get(); + + plat = dev_get_platdata(di->parent->dev); + + /* get btemp specific platform data */ + if (!plat->btemp) { + dev_err(di->dev, "no btemp platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->pdata = plat->btemp; + + /* get battery specific platform data */ + if (!plat->battery) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->bat = plat->battery; + + /* BTEMP supply */ + di->btemp_psy.name = "ab8500_btemp"; + di->btemp_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->btemp_psy.properties = ab8500_btemp_props; + di->btemp_psy.num_properties = ARRAY_SIZE(ab8500_btemp_props); + di->btemp_psy.get_property = ab8500_btemp_get_property; + di->btemp_psy.supplied_to = di->pdata->supplied_to; + di->btemp_psy.num_supplicants = di->pdata->num_supplicants; + di->btemp_psy.external_power_changed = + ab8500_btemp_external_power_changed; + + + /* Create a work queue for the btemp */ + di->btemp_wq = + create_singlethread_workqueue("ab8500_btemp_wq"); + if (di->btemp_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for measuring temperature periodically */ + INIT_DELAYED_WORK_DEFERRABLE(&di->btemp_periodic_work, + ab8500_btemp_periodic_work); + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(di->dev); + if (ret < 0) { + dev_err(di->dev, "failed to get chip ID\n"); + goto free_btemp_wq; + } + di->chip_id = ret; + dev_dbg(di->dev, "AB8500 CID is: 0x%02x\n", + di->chip_id); + + /* Identify the battery */ + if (ab8500_btemp_id(di) < 0) + dev_warn(di->dev, "failed to identify the battery\n"); + + /* Set BTEMP thermal limits. Low and Med are fixed */ + di->btemp_ranges.btemp_low_limit = BTEMP_THERMAL_LOW_LIMIT; + di->btemp_ranges.btemp_med_limit = BTEMP_THERMAL_MED_LIMIT; + + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_BTEMP_HIGH_TH, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + goto free_btemp_wq; + } + switch (val) { + case BTEMP_HIGH_TH_57_0: + case BTEMP_HIGH_TH_57_1: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_57; + break; + case BTEMP_HIGH_TH_52: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_52; + break; + case BTEMP_HIGH_TH_62: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_62; + break; + } + + /* Register BTEMP power supply class */ + ret = power_supply_register(di->dev, &di->btemp_psy); + if (ret) { + dev_err(di->dev, "failed to register BTEMP psy\n"); + goto free_btemp_wq; + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_btemp_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_btemp_irq[i].name, di); + + if (ret) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_btemp_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_btemp_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + /* Kick off periodic temperature measurements */ + ab8500_btemp_periodic(di, true); + list_add_tail(&di->node, &ab8500_btemp_list); + + return ret; + +free_irq: + power_supply_unregister(&di->btemp_psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + free_irq(irq, di); + } +free_btemp_wq: + destroy_workqueue(di->btemp_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_btemp_driver = { + .probe = ab8500_btemp_probe, + .remove = __devexit_p(ab8500_btemp_remove), + .suspend = ab8500_btemp_suspend, + .resume = ab8500_btemp_resume, + .driver = { + .name = "ab8500-btemp", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_btemp_init(void) +{ + return platform_driver_register(&ab8500_btemp_driver); +} + +static void __exit ab8500_btemp_exit(void) +{ + platform_driver_unregister(&ab8500_btemp_driver); +} + +subsys_initcall_sync(ab8500_btemp_init); +module_exit(ab8500_btemp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); +MODULE_ALIAS("platform:ab8500-btemp"); +MODULE_DESCRIPTION("AB8500 battery temperature driver"); diff --git a/drivers/power/ab8500_chargalg.c b/drivers/power/ab8500_chargalg.c new file mode 100644 index 00000000000..97ea736424a --- /dev/null +++ b/drivers/power/ab8500_chargalg.c @@ -0,0 +1,1961 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Charging algorithm driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: Johan Palsson <johan.palsson@stericsson.com> + * Author: Karl Komierowski <karl.komierowski@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/kobject.h> +#include <linux/mfd/ab8500.h> +#include <linux/mfd/ab8500/ux500_chargalg.h> +#include <linux/mfd/ab8500/bm.h> +#include <linux/mfd/ab8500/gpadc.h> + +/* Watchdog kick interval */ +#define CHG_WD_INTERVAL (60 * HZ) + +/* End-of-charge criteria counter */ +#define EOC_COND_CNT 10 + +/* Recharge criteria counter */ +#define RCH_COND_CNT 3 + +#define to_ab8500_chargalg_device_info(x) container_of((x), \ + struct ab8500_chargalg, chargalg_psy); + +enum ab8500_chargers { + NO_CHG, + AC_CHG, + USB_CHG, +}; + +struct ab8500_chargalg_charger_info { + enum ab8500_chargers conn_chg; + enum ab8500_chargers prev_conn_chg; + enum ab8500_chargers online_chg; + enum ab8500_chargers prev_online_chg; + enum ab8500_chargers charger_type; + bool usb_chg_ok; + bool ac_chg_ok; + int usb_volt; + int usb_curr; + int ac_volt; + int ac_curr; + int usb_vset; + int usb_iset; + int ac_vset; + int ac_iset; +}; + +struct ab8500_chargalg_suspension_status { + bool suspended_change; + bool ac_suspended; + bool usb_suspended; +}; + +struct ab8500_chargalg_battery_data { + int temp; + int volt; + int avg_curr; + int inst_curr; + int percent; +}; + +enum ab8500_chargalg_states { + STATE_HANDHELD_INIT, + STATE_HANDHELD, + STATE_CHG_NOT_OK_INIT, + STATE_CHG_NOT_OK, + STATE_HW_TEMP_PROTECT_INIT, + STATE_HW_TEMP_PROTECT, + STATE_NORMAL_INIT, + STATE_NORMAL, + STATE_WAIT_FOR_RECHARGE_INIT, + STATE_WAIT_FOR_RECHARGE, + STATE_MAINTENANCE_A_INIT, + STATE_MAINTENANCE_A, + STATE_MAINTENANCE_B_INIT, + STATE_MAINTENANCE_B, + STATE_TEMP_UNDEROVER_INIT, + STATE_TEMP_UNDEROVER, + STATE_TEMP_LOWHIGH_INIT, + STATE_TEMP_LOWHIGH, + STATE_SUSPENDED_INIT, + STATE_SUSPENDED, + STATE_OVV_PROTECT_INIT, + STATE_OVV_PROTECT, + STATE_SAFETY_TIMER_EXPIRED_INIT, + STATE_SAFETY_TIMER_EXPIRED, + STATE_BATT_REMOVED_INIT, + STATE_BATT_REMOVED, + STATE_WD_EXPIRED_INIT, + STATE_WD_EXPIRED, +}; + +static const char *states[] = { + "HANDHELD_INIT", + "HANDHELD", + "CHG_NOT_OK_INIT", + "CHG_NOT_OK", + "HW_TEMP_PROTECT_INIT", + "HW_TEMP_PROTECT", + "NORMAL_INIT", + "NORMAL", + "WAIT_FOR_RECHARGE_INIT", + "WAIT_FOR_RECHARGE", + "MAINTENANCE_A_INIT", + "MAINTENANCE_A", + "MAINTENANCE_B_INIT", + "MAINTENANCE_B", + "TEMP_UNDEROVER_INIT", + "TEMP_UNDEROVER", + "TEMP_LOWHIGH_INIT", + "TEMP_LOWHIGH", + "SUSPENDED_INIT", + "SUSPENDED", + "OVV_PROTECT_INIT", + "OVV_PROTECT", + "SAFETY_TIMER_EXPIRED_INIT", + "SAFETY_TIMER_EXPIRED", + "BATT_REMOVED_INIT", + "BATT_REMOVED", + "WD_EXPIRED_INIT", + "WD_EXPIRED", +}; + +struct ab8500_chargalg_events { + bool batt_unknown; + bool mainextchnotok; + bool batt_ovv; + bool batt_rem; + bool btemp_underover; + bool btemp_lowhigh; + bool main_thermal_prot; + bool usb_thermal_prot; + bool main_ovv; + bool vbus_ovv; + bool usbchargernotok; + bool safety_timer_expired; + bool maintenance_timer_expired; + bool ac_wd_expired; + bool usb_wd_expired; + bool ac_cv_active; + bool usb_cv_active; + bool vbus_collapsed; +}; + +/** + * struct ab8500_charge_curr_maximization - Charger maximization parameters + * @original_iset: the non optimized/maximised charger current + * @current_iset: the charging current used at this moment + * @test_delta_i: the delta between the current we want to charge and the + current that is really going into the battery + * @condition_cnt: number of iterations needed before a new charger current + is set + * @max_current: maximum charger current + * @wait_cnt: to avoid too fast current step down in case of charger + * voltage collapse, we insert this delay between step + * down + * @level: tells in how many steps the charging current has been + increased + */ +struct ab8500_charge_curr_maximization { + int original_iset; + int current_iset; + int test_delta_i; + int condition_cnt; + int max_current; + int wait_cnt; + u8 level; +}; + +enum maxim_ret { + MAXIM_RET_NOACTION, + MAXIM_RET_CHANGE, + MAXIM_RET_IBAT_TOO_HIGH, +}; + +/** + * struct ab8500_chargalg - ab8500 Charging algorithm device information + * @dev: pointer to the structure device + * @charge_status: battery operating status + * @eoc_cnt: counter used to determine end-of_charge + * @rch_cnt: counter used to determine start of recharge + * @maintenance_chg: indicate if maintenance charge is active + * @t_hyst_norm temperature hysteresis when the temperature has been + * over or under normal limits + * @t_hyst_lowhigh temperature hysteresis when the temperature has been + * over or under the high or low limits + * @charge_state: current state of the charging algorithm + * @ccm charging current maximization parameters + * @chg_info: information about connected charger types + * @batt_data: data of the battery + * @susp_status: current charger suspension status + * @parent: pointer to the struct ab8500 + * @pdata: pointer to the ab8500_chargalg platform data + * @bat: pointer to the ab8500_bm platform data + * @chargalg_psy: structure that holds the battery properties exposed by + * the charging algorithm + * @events: structure for information about events triggered + * @chargalg_wq: work queue for running the charging algorithm + * @chargalg_periodic_work: work to run the charging algorithm periodically + * @chargalg_wd_work: work to kick the charger watchdog periodically + * @chargalg_work: work to run the charging algorithm instantly + * @safety_timer: charging safety timer + * @maintenance_timer: maintenance charging timer + * @chargalg_kobject: structure of type kobject + */ +struct ab8500_chargalg { + struct device *dev; + int charge_status; + int eoc_cnt; + int rch_cnt; + bool maintenance_chg; + int t_hyst_norm; + int t_hyst_lowhigh; + enum ab8500_chargalg_states charge_state; + struct ab8500_charge_curr_maximization ccm; + struct ab8500_chargalg_charger_info chg_info; + struct ab8500_chargalg_battery_data batt_data; + struct ab8500_chargalg_suspension_status susp_status; + struct ab8500 *parent; + struct ab8500_chargalg_platform_data *pdata; + struct ab8500_bm_data *bat; + struct power_supply chargalg_psy; + struct ux500_charger *ac_chg; + struct ux500_charger *usb_chg; + struct ab8500_chargalg_events events; + struct workqueue_struct *chargalg_wq; + struct delayed_work chargalg_periodic_work; + struct delayed_work chargalg_wd_work; + struct work_struct chargalg_work; + struct timer_list safety_timer; + struct timer_list maintenance_timer; + struct kobject chargalg_kobject; +}; + +/* Main battery properties */ +static enum power_supply_property ab8500_chargalg_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, +}; + +/** + * ab8500_chargalg_safety_timer_expired() - Expiration of the safety timer + * @data: pointer to the ab8500_chargalg structure + * + * This function gets called when the safety timer for the charger + * expires + */ +static void ab8500_chargalg_safety_timer_expired(unsigned long data) +{ + struct ab8500_chargalg *di = (struct ab8500_chargalg *) data; + dev_err(di->dev, "Safety timer expired\n"); + di->events.safety_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * ab8500_chargalg_maintenance_timer_expired() - Expiration of + * the maintenance timer + * @i: pointer to the ab8500_chargalg structure + * + * This function gets called when the maintenence timer + * expires + */ +static void ab8500_chargalg_maintenance_timer_expired(unsigned long data) +{ + + struct ab8500_chargalg *di = (struct ab8500_chargalg *) data; + dev_dbg(di->dev, "Maintenance timer expired\n"); + di->events.maintenance_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * ab8500_chargalg_state_to() - Change charge state + * @di: pointer to the ab8500_chargalg structure + * + * This function gets called when a charge state change should occur + */ +static void ab8500_chargalg_state_to(struct ab8500_chargalg *di, + enum ab8500_chargalg_states state) +{ + dev_dbg(di->dev, + "State changed: %s (From state: [%d] %s =to=> [%d] %s )\n", + di->charge_state == state ? "NO" : "YES", + di->charge_state, + states[di->charge_state], + state, + states[state]); + + di->charge_state = state; +} + +/** + * ab8500_chargalg_check_charger_connection() - Check charger connection change + * @di: pointer to the ab8500_chargalg structure + * + * This function will check if there is a change in the charger connection + * and change charge state accordingly. AC has precedence over USB. + */ +static int ab8500_chargalg_check_charger_connection(struct ab8500_chargalg *di) +{ + if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg || + di->susp_status.suspended_change) { + /* + * Charger state changed or suspension + * has changed since last update + */ + if ((di->chg_info.conn_chg & AC_CHG) && + !di->susp_status.ac_suspended) { + dev_dbg(di->dev, "Charging source is AC\n"); + if (di->chg_info.charger_type != AC_CHG) { + di->chg_info.charger_type = AC_CHG; + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + } else if ((di->chg_info.conn_chg & USB_CHG) && + !di->susp_status.usb_suspended) { + dev_dbg(di->dev, "Charging source is USB\n"); + di->chg_info.charger_type = USB_CHG; + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + } else if (di->chg_info.conn_chg && + (di->susp_status.ac_suspended || + di->susp_status.usb_suspended)) { + dev_dbg(di->dev, "Charging is suspended\n"); + di->chg_info.charger_type = NO_CHG; + ab8500_chargalg_state_to(di, STATE_SUSPENDED_INIT); + } else { + dev_dbg(di->dev, "Charging source is OFF\n"); + di->chg_info.charger_type = NO_CHG; + ab8500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + di->chg_info.prev_conn_chg = di->chg_info.conn_chg; + di->susp_status.suspended_change = false; + } + return di->chg_info.conn_chg; +} + +/** + * ab8500_chargalg_start_safety_timer() - Start charging safety timer + * @di: pointer to the ab8500_chargalg structure + * + * The safety timer is used to avoid overcharging of old or bad batteries. + * There are different timers for AC and USB + */ +static void ab8500_chargalg_start_safety_timer(struct ab8500_chargalg *di) +{ + unsigned long timer_expiration = 0; + + switch (di->chg_info.charger_type) { + case AC_CHG: + timer_expiration = + round_jiffies(jiffies + + (di->bat->main_safety_tmr_h * 3600 * HZ)); + break; + + case USB_CHG: + timer_expiration = + round_jiffies(jiffies + + (di->bat->usb_safety_tmr_h * 3600 * HZ)); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } + + di->events.safety_timer_expired = false; + di->safety_timer.expires = timer_expiration; + if (!timer_pending(&di->safety_timer)) + add_timer(&di->safety_timer); + else + mod_timer(&di->safety_timer, timer_expiration); +} + +/** + * ab8500_chargalg_stop_safety_timer() - Stop charging safety timer + * @di: pointer to the ab8500_chargalg structure + * + * The safety timer is stopped whenever the NORMAL state is exited + */ +static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di) +{ + di->events.safety_timer_expired = false; + if (timer_pending(&di->safety_timer)) + del_timer(&di->safety_timer); +} + +/** + * ab8500_chargalg_start_maintenance_timer() - Start charging maintenance timer + * @di: pointer to the ab8500_chargalg structure + * @duration: duration of ther maintenance timer in hours + * + * The maintenance timer is used to maintain the charge in the battery once + * the battery is considered full. These timers are chosen to match the + * discharge curve of the battery + */ +static void ab8500_chargalg_start_maintenance_timer(struct ab8500_chargalg *di, + int duration) +{ + unsigned long timer_expiration; + + /* Convert from hours to jiffies */ + timer_expiration = round_jiffies(jiffies + (duration * 3600 * HZ)); + + di->events.maintenance_timer_expired = false; + di->maintenance_timer.expires = timer_expiration; + if (!timer_pending(&di->maintenance_timer)) + add_timer(&di->maintenance_timer); + else + mod_timer(&di->maintenance_timer, timer_expiration); +} + +/** + * ab8500_chargalg_stop_maintenance_timer() - Stop maintenance timer + * @di: pointer to the ab8500_chargalg structure + * + * The maintenance timer is stopped whenever maintenance ends or when another + * state is entered + */ +static void ab8500_chargalg_stop_maintenance_timer(struct ab8500_chargalg *di) +{ + di->events.maintenance_timer_expired = false; + del_timer(&di->maintenance_timer); +} + +/** + * ab8500_chargalg_kick_watchdog() - Kick charger watchdog + * @di: pointer to the ab8500_chargalg structure + * + * The charger watchdog have to be kicked periodically whenever the charger is + * on, else the ABB will reset the system + */ +static int ab8500_chargalg_kick_watchdog(struct ab8500_chargalg *di) +{ + /* Check if charger exists and kick watchdog if charging */ + if (di->ac_chg && di->ac_chg->ops.kick_wd && + di->chg_info.online_chg & AC_CHG) + return di->ac_chg->ops.kick_wd(di->ac_chg); + else if (di->usb_chg && di->usb_chg->ops.kick_wd && + di->chg_info.online_chg & USB_CHG) + return di->usb_chg->ops.kick_wd(di->usb_chg); + + return -ENXIO; +} + +/** + * ab8500_chargalg_ac_en() - Turn on/off the AC charger + * @di: pointer to the ab8500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The AC charger will be turned on/off with the requested charge voltage and + * current + */ +static int ab8500_chargalg_ac_en(struct ab8500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->ac_chg || !di->ac_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->ac_chg->max_out_volt) + vset = min(vset, di->ac_chg->max_out_volt); + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + di->chg_info.ac_iset = iset; + di->chg_info.ac_vset = vset; + + return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset); +} + +/** + * ab8500_chargalg_usb_en() - Turn on/off the USB charger + * @di: pointer to the ab8500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The USB charger will be turned on/off with the requested charge voltage and + * current + */ +static int ab8500_chargalg_usb_en(struct ab8500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->usb_chg || !di->usb_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->usb_chg->max_out_volt) + vset = min(vset, di->usb_chg->max_out_volt); + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + di->chg_info.usb_iset = iset; + di->chg_info.usb_vset = vset; + + return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset); +} + +/** + * ab8500_chargalg_update_chg_curr() - Update charger current + * @di: pointer to the ab8500_chargalg structure + * @iset: requested charger output current + * + * The charger output current will be updated for the charger + * that is currently in use + */ +static int ab8500_chargalg_update_chg_curr(struct ab8500_chargalg *di, + int iset) +{ + /* Check if charger exists and update current if charging */ + if (di->ac_chg && di->ac_chg->ops.update_curr && + di->chg_info.charger_type & AC_CHG) { + /* + * Select maximum of what both the charger + * and the battery supports + */ + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + di->chg_info.ac_iset = iset; + + return di->ac_chg->ops.update_curr(di->ac_chg, iset); + } else if (di->usb_chg && di->usb_chg->ops.update_curr && + di->chg_info.charger_type & USB_CHG) { + /* + * Select maximum of what both the charger + * and the battery supports + */ + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + di->chg_info.usb_iset = iset; + + return di->usb_chg->ops.update_curr(di->usb_chg, iset); + } + + return -ENXIO; +} + +/** + * ab8500_chargalg_stop_charging() - Stop charging + * @di: pointer to the ab8500_chargalg structure + * + * This function is called from any state where charging should be stopped. + * All charging is disabled and all status parameters and timers are changed + * accordingly + */ +static void ab8500_chargalg_stop_charging(struct ab8500_chargalg *di) +{ + ab8500_chargalg_ac_en(di, false, 0, 0); + ab8500_chargalg_usb_en(di, false, 0, 0); + ab8500_chargalg_stop_safety_timer(di); + ab8500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(&di->chargalg_psy); +} + +/** + * ab8500_chargalg_hold_charging() - Pauses charging + * @di: pointer to the ab8500_chargalg structure + * + * This function is called in the case where maintenance charging has been + * disabled and instead a battery voltage mode is entered to check when the + * battery voltage has reached a certain recharge voltage + */ +static void ab8500_chargalg_hold_charging(struct ab8500_chargalg *di) +{ + ab8500_chargalg_ac_en(di, false, 0, 0); + ab8500_chargalg_usb_en(di, false, 0, 0); + ab8500_chargalg_stop_safety_timer(di); + ab8500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(&di->chargalg_psy); +} + +/** + * ab8500_chargalg_start_charging() - Start the charger + * @di: pointer to the ab8500_chargalg structure + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * A charger will be enabled depending on the requested charger type that was + * detected previously. + */ +static void ab8500_chargalg_start_charging(struct ab8500_chargalg *di, + int vset, int iset) +{ + switch (di->chg_info.charger_type) { + case AC_CHG: + dev_dbg(di->dev, + "AC parameters: Vset %d, Ich %d\n", vset, iset); + ab8500_chargalg_usb_en(di, false, 0, 0); + ab8500_chargalg_ac_en(di, true, vset, iset); + break; + + case USB_CHG: + dev_dbg(di->dev, + "USB parameters: Vset %d, Ich %d\n", vset, iset); + ab8500_chargalg_ac_en(di, false, 0, 0); + ab8500_chargalg_usb_en(di, true, vset, iset); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } +} + +/** + * ab8500_chargalg_check_temp() - Check battery temperature ranges + * @di: pointer to the ab8500_chargalg structure + * + * The battery temperature is checked against the predefined limits and the + * charge state is changed accordingly + */ +static void ab8500_chargalg_check_temp(struct ab8500_chargalg *di) +{ + if (di->batt_data.temp > (di->bat->temp_low + di->t_hyst_norm) && + di->batt_data.temp < (di->bat->temp_high - di->t_hyst_norm)) { + /* Temp OK! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = 0; + } else { + if (((di->batt_data.temp >= di->bat->temp_high) && + (di->batt_data.temp < + (di->bat->temp_over - di->t_hyst_lowhigh))) || + ((di->batt_data.temp > + (di->bat->temp_under + di->t_hyst_lowhigh)) && + (di->batt_data.temp <= di->bat->temp_low))) { + /* TEMP minor!!!!! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = true; + di->t_hyst_norm = di->bat->temp_hysteresis; + di->t_hyst_lowhigh = 0; + } else if (di->batt_data.temp <= di->bat->temp_under || + di->batt_data.temp >= di->bat->temp_over) { + /* TEMP major!!!!! */ + di->events.btemp_underover = true; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = di->bat->temp_hysteresis; + } else { + /* Within hysteresis */ + dev_dbg(di->dev, "Within hysteresis limit temp: %d " + "hyst_lowhigh %d, hyst normal %d\n", + di->batt_data.temp, di->t_hyst_lowhigh, + di->t_hyst_norm); + } + } +} + +/** + * ab8500_chargalg_check_charger_voltage() - Check charger voltage + * @di: pointer to the ab8500_chargalg structure + * + * Charger voltage is checked against maximum limit + */ +static void ab8500_chargalg_check_charger_voltage(struct ab8500_chargalg *di) +{ + if (di->chg_info.usb_volt > di->bat->chg_params->usb_volt_max) + di->chg_info.usb_chg_ok = false; + else + di->chg_info.usb_chg_ok = true; + + if (di->chg_info.ac_volt > di->bat->chg_params->ac_volt_max) + di->chg_info.ac_chg_ok = false; + else + di->chg_info.ac_chg_ok = true; + +} + +/** + * ab8500_chargalg_end_of_charge() - Check if end-of-charge criteria is fulfilled + * @di: pointer to the ab8500_chargalg structure + * + * End-of-charge criteria is fulfilled when the battery voltage is above a + * certain limit and the battery current is below a certain limit for a + * predefined number of consecutive seconds. If true, the battery is full + */ +static void ab8500_chargalg_end_of_charge(struct ab8500_chargalg *di) +{ + if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING && + di->charge_state == STATE_NORMAL && + !di->maintenance_chg && (di->batt_data.volt >= + di->bat->bat_type[di->bat->batt_id].termination_vol || + di->events.usb_cv_active || di->events.ac_cv_active) && + di->batt_data.avg_curr < + di->bat->bat_type[di->bat->batt_id].termination_curr && + di->batt_data.avg_curr > 0) { + if (++di->eoc_cnt >= EOC_COND_CNT) { + di->eoc_cnt = 0; + di->charge_status = POWER_SUPPLY_STATUS_FULL; + di->maintenance_chg = true; + dev_dbg(di->dev, "EOC reached!\n"); + power_supply_changed(&di->chargalg_psy); + } else { + dev_dbg(di->dev, + " EOC limit reached for the %d" + " time, out of %d before EOC\n", + di->eoc_cnt, + EOC_COND_CNT); + } + } else { + di->eoc_cnt = 0; + } +} + +static void init_maxim_chg_curr(struct ab8500_chargalg *di) +{ + di->ccm.original_iset = + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl; + di->ccm.current_iset = + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl; + di->ccm.test_delta_i = di->bat->maxi->charger_curr_step; + di->ccm.max_current = di->bat->maxi->chg_curr; + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.level = 0; +} + +/** + * ab8500_chargalg_chg_curr_maxim - increases the charger current to + * compensate for the system load + * @di pointer to the ab8500_chargalg structure + * + * This maximization function is used to raise the charger current to get the + * battery current as close to the optimal value as possible. The battery + * current during charging is affected by the system load + */ +static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di) +{ + int delta_i; + + if (!di->bat->maxi->ena_maxi) + return MAXIM_RET_NOACTION; + + delta_i = di->ccm.original_iset - di->batt_data.inst_curr; + + if (di->events.vbus_collapsed) { + dev_dbg(di->dev, "Charger voltage has collapsed %d\n", + di->ccm.wait_cnt); + if (di->ccm.wait_cnt == 0) { + dev_dbg(di->dev, "lowering current\n"); + di->ccm.wait_cnt++; + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.max_current = + di->ccm.current_iset - di->ccm.test_delta_i; + di->ccm.current_iset = di->ccm.max_current; + di->ccm.level--; + return MAXIM_RET_CHANGE; + } else { + dev_dbg(di->dev, "waiting\n"); + /* Let's go in here twice before lowering curr again */ + di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 3; + return MAXIM_RET_NOACTION; + } + } + + di->ccm.wait_cnt = 0; + + if ((di->batt_data.inst_curr > di->ccm.original_iset)) { + dev_dbg(di->dev, " Maximization Ibat (%dmA) too high" + " (limit %dmA) (current iset: %dmA)!\n", + di->batt_data.inst_curr, di->ccm.original_iset, + di->ccm.current_iset); + + if (di->ccm.current_iset == di->ccm.original_iset) + return MAXIM_RET_NOACTION; + + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.current_iset = di->ccm.original_iset; + di->ccm.level = 0; + + return MAXIM_RET_IBAT_TOO_HIGH; + } + + if (delta_i > di->ccm.test_delta_i && + (di->ccm.current_iset + di->ccm.test_delta_i) < + di->ccm.max_current) { + if (di->ccm.condition_cnt-- == 0) { + /* Increse the iset with cco.test_delta_i */ + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.current_iset += di->ccm.test_delta_i; + di->ccm.level++; + dev_dbg(di->dev, " Maximization needed, increase" + " with %d mA to %dmA (Optimal ibat: %d)" + " Level %d\n", + di->ccm.test_delta_i, + di->ccm.current_iset, + di->ccm.original_iset, + di->ccm.level); + return MAXIM_RET_CHANGE; + } else { + return MAXIM_RET_NOACTION; + } + } else { + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + return MAXIM_RET_NOACTION; + } +} + +static void handle_maxim_chg_curr(struct ab8500_chargalg *di) +{ + enum maxim_ret ret; + int result; + + ret = ab8500_chargalg_chg_curr_maxim(di); + switch (ret) { + case MAXIM_RET_CHANGE: + result = ab8500_chargalg_update_chg_curr(di, + di->ccm.current_iset); + if (result) + dev_err(di->dev, "failed to set chg curr\n"); + break; + case MAXIM_RET_IBAT_TOO_HIGH: + result = ab8500_chargalg_update_chg_curr(di, + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + if (result) + dev_err(di->dev, "failed to set chg curr\n"); + break; + + case MAXIM_RET_NOACTION: + default: + /* Do nothing..*/ + break; + } +} + +static void ab8500_chargalg_check_safety_timer(struct ab8500_chargalg *di) +{ + /* + * The safety timer will not be started until the capacity reported + * from the FG algorithm is 100%. Then we know that the amount of + * charge that's gone into the battery is enough for the battery + * to be full. If it has not reached end-of-charge before the safety + * timer has expired then we know that the battery is overcharged + * and charging will be stopped to protect the battery. + */ + if (di->batt_data.percent == 100 && + !timer_pending(&di->safety_timer)) { + ab8500_chargalg_start_safety_timer(di); + dev_dbg(di->dev, "start safety timer\n"); + } else if (di->batt_data.percent != 100 && + timer_pending(&di->safety_timer)) { + ab8500_chargalg_stop_safety_timer(di); + dev_dbg(di->dev, "stop safety timer\n"); + } +} + +static int ab8500_chargalg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_chargalg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_chargalg_device_info(psy); + + /* For all psy where the driver name appears in any supplied_to */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + /* Initialize chargers if not already done */ + if (!di->ac_chg && + ext->type == POWER_SUPPLY_TYPE_MAINS) + di->ac_chg = psy_to_ux500_charger(ext); + else if (!di->usb_chg && + ext->type == POWER_SUPPLY_TYPE_USB) + di->usb_chg = psy_to_ux500_charger(ext); + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + /* Battery present */ + if (ret.intval) + di->events.batt_rem = false; + /* Battery removed */ + else + di->events.batt_rem = true; + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~AC_CHG; + } + /* AC connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= AC_CHG; + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~USB_CHG; + } + /* USB connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= USB_CHG; + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_ONLINE: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC offline */ + if (!ret.intval && + (di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~AC_CHG; + } + /* AC online */ + else if (ret.intval && + !(di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= AC_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB offline */ + if (!ret.intval && + (di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~USB_CHG; + } + /* USB online */ + else if (ret.intval && + !(di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= USB_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_HEALTH: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.mainextchnotok = true; + di->events.main_thermal_prot = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.ac_wd_expired = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.main_thermal_prot = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.main_thermal_prot = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.main_ovv = true; + di->events.mainextchnotok = false; + di->events.main_thermal_prot = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.main_thermal_prot = false; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + default: + break; + } + break; + + case POWER_SUPPLY_TYPE_USB: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.usbchargernotok = true; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.usb_wd_expired = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.usb_thermal_prot = true; + di->events.usbchargernotok = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.vbus_ovv = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + default: + break; + } + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_volt = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.ac_cv_active = true; + else + di->events.ac_cv_active = false; + + break; + case POWER_SUPPLY_TYPE_USB: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.usb_cv_active = true; + else + di->events.usb_cv_active = false; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (ret.intval) + di->events.batt_unknown = false; + else + di->events.batt_unknown = true; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TEMP: + di->batt_data.temp = ret.intval / 10; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.inst_curr = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_CURRENT_AVG: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.avg_curr = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + if (ret.intval) + di->events.vbus_collapsed = true; + else + di->events.vbus_collapsed = false; + break; + default: + break; + } + break; + case POWER_SUPPLY_PROP_CAPACITY: + di->batt_data.percent = ret.intval; + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_chargalg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void ab8500_chargalg_external_power_changed(struct power_supply *psy) +{ + struct ab8500_chargalg *di = to_ab8500_chargalg_device_info(psy); + + /* + * Trigger execution of the algorithm instantly and read + * all power_supply properties there instead + */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * ab8500_chargalg_algorithm() - Main function for the algorithm + * @di: pointer to the ab8500_chargalg structure + * + * This is the main control function for the charging algorithm. + * It is called periodically or when something happens that will + * trigger a state change + */ +static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) +{ + int charger_status; + + /* Collect data from all power_supply class devices */ + class_for_each_device(power_supply_class, NULL, + &di->chargalg_psy, ab8500_chargalg_get_ext_psy_data); + + ab8500_chargalg_end_of_charge(di); + ab8500_chargalg_check_temp(di); + ab8500_chargalg_check_charger_voltage(di); + charger_status = ab8500_chargalg_check_charger_connection(di); + + /* + * First check if we have a charger connected. + * Also we don't allow charging of unknown batteries if configured + * this way + */ + if (!charger_status || + (di->events.batt_unknown && !di->bat->chg_unknown_bat)) { + if (di->charge_state != STATE_HANDHELD) { + di->events.safety_timer_expired = false; + ab8500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + } + + /* If suspended, we should not continue checking the flags */ + else if (di->charge_state == STATE_SUSPENDED_INIT || + di->charge_state == STATE_SUSPENDED) { + /* We don't do anything here, just don,t continue */ + } + + /* Safety timer expiration */ + else if (di->events.safety_timer_expired) { + if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED) + ab8500_chargalg_state_to(di, + STATE_SAFETY_TIMER_EXPIRED_INIT); + } + /* + * Check if any interrupts has occured + * that will prevent us from charging + */ + + /* Battery removed */ + else if (di->events.batt_rem) { + if (di->charge_state != STATE_BATT_REMOVED) + ab8500_chargalg_state_to(di, STATE_BATT_REMOVED_INIT); + } + /* Main or USB charger not ok. */ + else if (di->events.mainextchnotok || di->events.usbchargernotok) { + /* + * If vbus_collapsed is set, we have to lower the charger + * current, which is done in the normal state below + */ + if (di->charge_state != STATE_CHG_NOT_OK && + !di->events.vbus_collapsed) + ab8500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT); + } + /* VBUS, Main or VBAT OVV. */ + else if (di->events.vbus_ovv || + di->events.main_ovv || + di->events.batt_ovv || + !di->chg_info.usb_chg_ok || + !di->chg_info.ac_chg_ok) { + if (di->charge_state != STATE_OVV_PROTECT) + ab8500_chargalg_state_to(di, STATE_OVV_PROTECT_INIT); + } + /* USB Thermal, stop charging */ + else if (di->events.main_thermal_prot || + di->events.usb_thermal_prot) { + if (di->charge_state != STATE_HW_TEMP_PROTECT) + ab8500_chargalg_state_to(di, + STATE_HW_TEMP_PROTECT_INIT); + } + /* Battery temp over/under */ + else if (di->events.btemp_underover) { + if (di->charge_state != STATE_TEMP_UNDEROVER) + ab8500_chargalg_state_to(di, + STATE_TEMP_UNDEROVER_INIT); + } + /* Watchdog expired */ + else if (di->events.ac_wd_expired || + di->events.usb_wd_expired) { + if (di->charge_state != STATE_WD_EXPIRED) + ab8500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT); + } + /* Battery temp high/low */ + else if (di->events.btemp_lowhigh) { + if (di->charge_state != STATE_TEMP_LOWHIGH) + ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT); + } + + dev_dbg(di->dev, + "[CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d " + "State %s Active_chg %d Chg_status %d AC %d USB %d " + "AC_online %d USB_online %d AC_CV %d USB_CV %d AC_I %d " + "USB_I %d AC_Vset %d AC_Iset %d USB_Vset %d USB_Iset %d\n", + di->batt_data.volt, + di->batt_data.avg_curr, + di->batt_data.inst_curr, + di->batt_data.temp, + di->batt_data.percent, + di->maintenance_chg, + states[di->charge_state], + di->chg_info.charger_type, + di->charge_status, + di->chg_info.conn_chg & AC_CHG, + di->chg_info.conn_chg & USB_CHG, + di->chg_info.online_chg & AC_CHG, + di->chg_info.online_chg & USB_CHG, + di->events.ac_cv_active, + di->events.usb_cv_active, + di->chg_info.ac_curr, + di->chg_info.usb_curr, + di->chg_info.ac_vset, + di->chg_info.ac_iset, + di->chg_info.usb_vset, + di->chg_info.usb_iset); + + switch (di->charge_state) { + case STATE_HANDHELD_INIT: + ab8500_chargalg_stop_charging(di); + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + ab8500_chargalg_state_to(di, STATE_HANDHELD); + /* Intentional fallthrough */ + + case STATE_HANDHELD: + break; + + case STATE_SUSPENDED_INIT: + if (di->susp_status.ac_suspended) + ab8500_chargalg_ac_en(di, false, 0, 0); + if (di->susp_status.usb_suspended) + ab8500_chargalg_usb_en(di, false, 0, 0); + ab8500_chargalg_stop_safety_timer(di); + ab8500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + ab8500_chargalg_state_to(di, STATE_SUSPENDED); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_SUSPENDED: + /* CHARGING is suspended */ + break; + + case STATE_BATT_REMOVED_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_BATT_REMOVED); + /* Intentional fallthrough */ + + case STATE_BATT_REMOVED: + if (!di->events.batt_rem) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_HW_TEMP_PROTECT_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_HW_TEMP_PROTECT); + /* Intentional fallthrough */ + + case STATE_HW_TEMP_PROTECT: + if (!di->events.main_thermal_prot && + !di->events.usb_thermal_prot) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_OVV_PROTECT_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_OVV_PROTECT); + /* Intentional fallthrough */ + + case STATE_OVV_PROTECT: + if (!di->events.vbus_ovv && + !di->events.main_ovv && + !di->events.batt_ovv && + di->chg_info.usb_chg_ok && + di->chg_info.ac_chg_ok) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_CHG_NOT_OK_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_CHG_NOT_OK); + /* Intentional fallthrough */ + + case STATE_CHG_NOT_OK: + if (!di->events.mainextchnotok && + !di->events.usbchargernotok) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_SAFETY_TIMER_EXPIRED_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_SAFETY_TIMER_EXPIRED); + /* Intentional fallthrough */ + + case STATE_SAFETY_TIMER_EXPIRED: + /* We exit this state when charger is removed */ + break; + + case STATE_NORMAL_INIT: + ab8500_chargalg_start_charging(di, + di->bat->bat_type[di->bat->batt_id].normal_vol_lvl, + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + ab8500_chargalg_state_to(di, STATE_NORMAL); + ab8500_chargalg_stop_maintenance_timer(di); + init_maxim_chg_curr(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->eoc_cnt = 0; + di->maintenance_chg = false; + power_supply_changed(&di->chargalg_psy); + + break; + + case STATE_NORMAL: + handle_maxim_chg_curr(di); + if (di->charge_status == POWER_SUPPLY_STATUS_FULL && + di->maintenance_chg) { + if (di->bat->no_maintenance) + ab8500_chargalg_state_to(di, + STATE_WAIT_FOR_RECHARGE_INIT); + else + ab8500_chargalg_state_to(di, + STATE_MAINTENANCE_A_INIT); + } + /* Check whether we should start the safety timer or not */ + ab8500_chargalg_check_safety_timer(di); + break; + + /* This state will be used when the maintenance state is disabled */ + case STATE_WAIT_FOR_RECHARGE_INIT: + ab8500_chargalg_hold_charging(di); + ab8500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE); + di->rch_cnt = RCH_COND_CNT; + /* Intentional fallthrough */ + + case STATE_WAIT_FOR_RECHARGE: + if (di->batt_data.volt <= + di->bat->bat_type[di->bat->batt_id].recharge_vol) { + if (di->rch_cnt-- == 0) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + } else + di->rch_cnt = RCH_COND_CNT; + break; + + case STATE_MAINTENANCE_A_INIT: + ab8500_chargalg_stop_safety_timer(di); + ab8500_chargalg_start_maintenance_timer(di, + di->bat->bat_type[ + di->bat->batt_id].maint_a_chg_timer_h); + ab8500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].maint_a_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].maint_a_cur_lvl); + ab8500_chargalg_state_to(di, STATE_MAINTENANCE_A); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_A: + if (di->events.maintenance_timer_expired) { + ab8500_chargalg_stop_maintenance_timer(di); + ab8500_chargalg_state_to(di, STATE_MAINTENANCE_B_INIT); + } + break; + + case STATE_MAINTENANCE_B_INIT: + ab8500_chargalg_start_maintenance_timer(di, + di->bat->bat_type[ + di->bat->batt_id].maint_b_chg_timer_h); + ab8500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].maint_b_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].maint_b_cur_lvl); + ab8500_chargalg_state_to(di, STATE_MAINTENANCE_B); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_B: + if (di->events.maintenance_timer_expired) { + ab8500_chargalg_stop_maintenance_timer(di); + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + break; + + case STATE_TEMP_LOWHIGH_INIT: + ab8500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].low_high_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].low_high_cur_lvl); + ab8500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_TEMP_LOWHIGH: + if (!di->events.btemp_lowhigh) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_WD_EXPIRED_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_WD_EXPIRED); + /* Intentional fallthrough */ + + case STATE_WD_EXPIRED: + if (!di->events.ac_wd_expired && + !di->events.usb_wd_expired) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_TEMP_UNDEROVER_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_TEMP_UNDEROVER); + /* Intentional fallthrough */ + + case STATE_TEMP_UNDEROVER: + if (!di->events.btemp_underover) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + } + + /* Start charging directly if the new state is a charge state */ + if (di->charge_state == STATE_NORMAL_INIT || + di->charge_state == STATE_MAINTENANCE_A_INIT || + di->charge_state == STATE_MAINTENANCE_B_INIT) + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * ab8500_chargalg_periodic_work() - Periodic work for the algorithm + * @work: pointer to the work_struct structure + * + * Work queue function for the charging algorithm + */ +static void ab8500_chargalg_periodic_work(struct work_struct *work) +{ + struct ab8500_chargalg *di = container_of(work, + struct ab8500_chargalg, chargalg_periodic_work.work); + + ab8500_chargalg_algorithm(di); + + /* + * If a charger is connected then the battery has to be monitored + * frequently, else the work can be delayed. + */ + if (di->chg_info.conn_chg) + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bat->interval_charging * HZ); + else + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bat->interval_not_charging * HZ); +} + +/** + * ab8500_chargalg_wd_work() - periodic work to kick the charger watchdog + * @work: pointer to the work_struct structure + * + * Work queue function for kicking the charger watchdog + */ +static void ab8500_chargalg_wd_work(struct work_struct *work) +{ + int ret; + struct ab8500_chargalg *di = container_of(work, + struct ab8500_chargalg, chargalg_wd_work.work); + + dev_dbg(di->dev, "ab8500_chargalg_wd_work\n"); + + ret = ab8500_chargalg_kick_watchdog(di); + if (ret < 0) + dev_err(di->dev, "failed to kick watchdog\n"); + + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, CHG_WD_INTERVAL); +} + +/** + * ab8500_chargalg_work() - Work to run the charging algorithm instantly + * @work: pointer to the work_struct structure + * + * Work queue function for calling the charging algorithm + */ +static void ab8500_chargalg_work(struct work_struct *work) +{ + struct ab8500_chargalg *di = container_of(work, + struct ab8500_chargalg, chargalg_work); + + ab8500_chargalg_algorithm(di); +} + +/** + * ab8500_chargalg_get_property() - get the chargalg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * chargalg properties by reading the sysfs files. + * status: charging/discharging/full/unknown + * health: health of the battery + * Returns error code in case of failure else 0 on success + */ +static int ab8500_chargalg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_chargalg *di; + + di = to_ab8500_chargalg_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (di->events.batt_ovv) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (di->events.btemp_underover) { + if (di->batt_data.temp <= di->bat->temp_under) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + } else { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } + break; + default: + return -EINVAL; + } + return 0; +} + +/* Exposure to the sysfs interface */ + +/** + * ab8500_chargalg_sysfs_charger() - sysfs store operations + * @kobj: pointer to the struct kobject + * @attr: pointer to the struct attribute + * @buf: buffer that holds the parameter passed from userspace + * @length: length of the parameter passed + * + * Returns length of the buffer(input taken from user space) on success + * else error code on failure + * The operation to be performed on passing the parameters from the user space. + */ +static ssize_t ab8500_chargalg_sysfs_charger(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t length) +{ + struct ab8500_chargalg *di = container_of(kobj, + struct ab8500_chargalg, chargalg_kobject); + long int param; + int ac_usb; + int ret; + char entry = *attr->name; + + switch (entry) { + case 'c': + ret = strict_strtol(buf, 10, ¶m); + if (ret < 0) + return ret; + + ac_usb = param; + switch (ac_usb) { + case 0: + /* Disable charging */ + di->susp_status.ac_suspended = true; + di->susp_status.usb_suspended = true; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 1: + /* Enable AC Charging */ + di->susp_status.ac_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 2: + /* Enable USB charging */ + di->susp_status.usb_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + default: + dev_info(di->dev, "Wrong input\n" + "Enter 0. Disable AC/USB Charging\n" + "1. Enable AC charging\n" + "2. Enable USB Charging\n"); + }; + break; + }; + return strlen(buf); +} + +static struct attribute ab8500_chargalg_en_charger = \ +{ + .name = "chargalg", + .mode = S_IWUGO, +}; + +static struct attribute *ab8500_chargalg_chg[] = { + &ab8500_chargalg_en_charger, + NULL +}; + +const struct sysfs_ops ab8500_chargalg_sysfs_ops = { + .store = ab8500_chargalg_sysfs_charger, +}; + +static struct kobj_type ab8500_chargalg_ktype = { + .sysfs_ops = &ab8500_chargalg_sysfs_ops, + .default_attrs = ab8500_chargalg_chg, +}; + +/** + * ab8500_chargalg_sysfs_exit() - de-init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function removes the entry in sysfs. + */ +static void ab8500_chargalg_sysfs_exit(struct ab8500_chargalg *di) +{ + kobject_del(&di->chargalg_kobject); +} + +/** + * ab8500_chargalg_sysfs_init() - init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function adds an entry in sysfs. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_chargalg_sysfs_init(struct ab8500_chargalg *di) +{ + int ret = 0; + + ret = kobject_init_and_add(&di->chargalg_kobject, + &ab8500_chargalg_ktype, + NULL, "ab8500_chargalg"); + if (ret < 0) + dev_err(di->dev, "failed to create sysfs entry\n"); + + return ret; +} +/* Exposure to the sysfs interface <<END>> */ + +#if defined(CONFIG_PM) +static int ab8500_chargalg_resume(struct platform_device *pdev) +{ + struct ab8500_chargalg *di = platform_get_drvdata(pdev); + + /* Kick charger watchdog if charging (any charger online) */ + if (di->chg_info.online_chg) + queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0); + + /* + * Run the charging algorithm directly to be sure we don't + * do it too seldom + */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + return 0; +} + +static int ab8500_chargalg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_chargalg *di = platform_get_drvdata(pdev); + + if (di->chg_info.online_chg) + cancel_delayed_work_sync(&di->chargalg_wd_work); + + cancel_delayed_work_sync(&di->chargalg_periodic_work); + + return 0; +} +#else +#define ab8500_chargalg_suspend NULL +#define ab8500_chargalg_resume NULL +#endif + +static int __devexit ab8500_chargalg_remove(struct platform_device *pdev) +{ + struct ab8500_chargalg *di = platform_get_drvdata(pdev); + + /* sysfs interface to enable/disbale charging from user space */ + ab8500_chargalg_sysfs_exit(di); + + /* Delete the work queue */ + destroy_workqueue(di->chargalg_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->chargalg_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab8500_chargalg_probe(struct platform_device *pdev) +{ + struct ab8500_platform_data *plat; + int ret = 0; + + struct ab8500_chargalg *di = + kzalloc(sizeof(struct ab8500_chargalg), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + + plat = dev_get_platdata(di->parent->dev); + + /* get chargalg specific platform data */ + if (!plat->chargalg) { + dev_err(di->dev, "no chargalg platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->pdata = plat->chargalg; + + /* get battery specific platform data */ + if (!plat->battery) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->bat = plat->battery; + + /* chargalg supply */ + di->chargalg_psy.name = "ab8500_chargalg"; + di->chargalg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->chargalg_psy.properties = ab8500_chargalg_props; + di->chargalg_psy.num_properties = ARRAY_SIZE(ab8500_chargalg_props); + di->chargalg_psy.get_property = ab8500_chargalg_get_property; + di->chargalg_psy.supplied_to = di->pdata->supplied_to; + di->chargalg_psy.num_supplicants = di->pdata->num_supplicants; + di->chargalg_psy.external_power_changed = + ab8500_chargalg_external_power_changed; + + /* Initilialize safety timer */ + init_timer(&di->safety_timer); + di->safety_timer.function = ab8500_chargalg_safety_timer_expired; + di->safety_timer.data = (unsigned long) di; + + /* Initilialize maintenance timer */ + init_timer(&di->maintenance_timer); + di->maintenance_timer.function = + ab8500_chargalg_maintenance_timer_expired; + di->maintenance_timer.data = (unsigned long) di; + + /* Create a work queue for the chargalg */ + di->chargalg_wq = + create_singlethread_workqueue("ab8500_chargalg_wq"); + if (di->chargalg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for chargalg */ + INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_periodic_work, + ab8500_chargalg_periodic_work); + INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_wd_work, + ab8500_chargalg_wd_work); + + /* Init work for chargalg */ + INIT_WORK(&di->chargalg_work, ab8500_chargalg_work); + + /* To detect charger at startup */ + di->chg_info.prev_conn_chg = -1; + + /* Register chargalg power supply class */ + ret = power_supply_register(di->dev, &di->chargalg_psy); + if (ret) { + dev_err(di->dev, "failed to register chargalg psy\n"); + goto free_chargalg_wq; + } + + platform_set_drvdata(pdev, di); + + /* sysfs interface to enable/disable charging from user space */ + ret = ab8500_chargalg_sysfs_init(di); + if (ret) { + dev_err(di->dev, "failed to create sysfs entry\n"); + goto free_psy; + } + + /* Run the charging algorithm */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + return ret; + +free_psy: + power_supply_unregister(&di->chargalg_psy); +free_chargalg_wq: + destroy_workqueue(di->chargalg_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_chargalg_driver = { + .probe = ab8500_chargalg_probe, + .remove = __devexit_p(ab8500_chargalg_remove), + .suspend = ab8500_chargalg_suspend, + .resume = ab8500_chargalg_resume, + .driver = { + .name = "ab8500-chargalg", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_chargalg_init(void) +{ + return platform_driver_register(&ab8500_chargalg_driver); +} + +static void __exit ab8500_chargalg_exit(void) +{ + platform_driver_unregister(&ab8500_chargalg_driver); +} + +module_init(ab8500_chargalg_init); +module_exit(ab8500_chargalg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab8500-chargalg"); +MODULE_DESCRIPTION("AB8500 battery temperature driver"); diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c new file mode 100644 index 00000000000..1b827a980ce --- /dev/null +++ b/drivers/power/ab8500_charger.c @@ -0,0 +1,2698 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Charger driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: Johan Palsson <johan.palsson@stericsson.com> + * Author: Karl Komierowski <karl.komierowski@stericsson.com> + * Author: Arun R Murthy <arun.murthy@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/completion.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/workqueue.h> +#include <linux/kobject.h> +#include <linux/mfd/ab8500.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/ab8500/bm.h> +#include <linux/mfd/ab8500/gpadc.h> +#include <linux/mfd/ab8500/ux500_chargalg.h> +#include <linux/usb/otg.h> + +/* Charger constants */ +#define NO_PW_CONN 0 +#define AC_PW_CONN 1 +#define USB_PW_CONN 2 + +#define MAIN_WDOG_ENA 0x01 +#define MAIN_WDOG_KICK 0x02 +#define MAIN_WDOG_DIS 0x00 +#define CHARG_WD_KICK 0x01 +#define MAIN_CH_ENA 0x01 +#define MAIN_CH_NO_OVERSHOOT_ENA_N 0x02 +#define USB_CH_ENA 0x01 +#define USB_CHG_NO_OVERSHOOT_ENA_N 0x02 +#define MAIN_CH_DET 0x01 +#define MAIN_CH_CV_ON 0x04 +#define USB_CH_CV_ON 0x08 +#define VBUS_DET_DBNC100 0x02 +#define VBUS_DET_DBNC1 0x01 +#define OTP_ENABLE_WD 0x01 + +#define MAIN_CH_INPUT_CURR_SHIFT 4 +#define VBUS_IN_CURR_LIM_SHIFT 4 + +#define LED_INDICATOR_PWM_ENA 0x01 +#define LED_INDICATOR_PWM_DIS 0x00 +#define LED_IND_CUR_5MA 0x04 +#define LED_INDICATOR_PWM_DUTY_252_256 0xBF + +/* HW failure constants */ +#define MAIN_CH_TH_PROT 0x02 +#define VBUS_CH_NOK 0x08 +#define USB_CH_TH_PROT 0x02 +#define VBUS_OVV_TH 0x01 +#define MAIN_CH_NOK 0x01 +#define VBUS_DET 0x80 + +/* UsbLineStatus register bit masks */ +#define AB8500_USB_LINK_STATUS 0x78 +#define AB8500_STD_HOST_SUSP 0x18 + +/* Watchdog timeout constant */ +#define WD_TIMER 0x30 /* 4min */ +#define WD_KICK_INTERVAL (60 * HZ) + +/* Lowest charger voltage is 3.39V -> 0x4E */ +#define LOW_VOLT_REG 0x4E + +/* UsbLineStatus register - usb types */ +enum ab8500_charger_link_status { + USB_STAT_NOT_CONFIGURED, + USB_STAT_STD_HOST_NC, + USB_STAT_STD_HOST_C_NS, + USB_STAT_STD_HOST_C_S, + USB_STAT_HOST_CHG_NM, + USB_STAT_HOST_CHG_HS, + USB_STAT_HOST_CHG_HS_CHIRP, + USB_STAT_DEDICATED_CHG, + USB_STAT_ACA_RID_A, + USB_STAT_ACA_RID_B, + USB_STAT_ACA_RID_C_NM, + USB_STAT_ACA_RID_C_HS, + USB_STAT_ACA_RID_C_HS_CHIRP, + USB_STAT_HM_IDGND, + USB_STAT_RESERVED, + USB_STAT_NOT_VALID_LINK, +}; + +enum ab8500_usb_state { + AB8500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */ + AB8500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */ + AB8500_BM_USB_STATE_CONFIGURED, + AB8500_BM_USB_STATE_SUSPEND, + AB8500_BM_USB_STATE_RESUME, + AB8500_BM_USB_STATE_MAX, +}; + +/* VBUS input current limits supported in AB8500 in mA */ +#define USB_CH_IP_CUR_LVL_0P05 50 +#define USB_CH_IP_CUR_LVL_0P09 98 +#define USB_CH_IP_CUR_LVL_0P19 193 +#define USB_CH_IP_CUR_LVL_0P29 290 +#define USB_CH_IP_CUR_LVL_0P38 380 +#define USB_CH_IP_CUR_LVL_0P45 450 +#define USB_CH_IP_CUR_LVL_0P5 500 +#define USB_CH_IP_CUR_LVL_0P6 600 +#define USB_CH_IP_CUR_LVL_0P7 700 +#define USB_CH_IP_CUR_LVL_0P8 800 +#define USB_CH_IP_CUR_LVL_0P9 900 +#define USB_CH_IP_CUR_LVL_1P0 1000 +#define USB_CH_IP_CUR_LVL_1P1 1100 +#define USB_CH_IP_CUR_LVL_1P3 1300 +#define USB_CH_IP_CUR_LVL_1P4 1400 +#define USB_CH_IP_CUR_LVL_1P5 1500 + +#define VBAT_3700 3700 + +#define to_ab8500_charger_usb_device_info(x) container_of((x), \ + struct ab8500_charger, usb_chg) +#define to_ab8500_charger_ac_device_info(x) container_of((x), \ + struct ab8500_charger, ac_chg) + +/** + * struct ab8500_charger_interrupts - ab8500 interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_charger_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab8500_charger_info { + int charger_connected; + int charger_online; + int charger_voltage; + int cv_active; + bool wd_expired; +}; + +struct ab8500_charger_event_flags { + bool mainextchnotok; + bool main_thermal_prot; + bool usb_thermal_prot; + bool vbus_ovv; + bool usbchargernotok; + bool chgwdexp; + bool vbus_collapse; +}; + +struct ab8500_charger_usb_state { + bool usb_changed; + int usb_current; + enum ab8500_usb_state state; + spinlock_t usb_lock; +}; + +/** + * struct ab8500_charger - ab8500 Charger device information + * @dev: Pointer to the structure device + * @chip_id: Chip-Id of the AB8500 + * @max_usb_in_curr: Max USB charger input current + * @vbus_detected: VBUS detected + * @vbus_detected_start: + * VBUS detected during startup + * @ac_conn: This will be true when the AC charger has been plugged + * @vddadc_en: Indicate if VDD ADC supply is enabled from this driver + * @vbat Battery voltage + * @old_vbat Previously measured battery voltage + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @pdata: Pointer to the ab8500_charger platform data + * @bat: Pointer to the ab8500_bm platform data + * @flags: Structure for information about events triggered + * @usb_state: Structure for usb stack information + * @ac_chg: AC charger power supply + * @usb_chg: USB charger power supply + * @ac: Structure that holds the AC charger properties + * @usb: Structure that holds the USB charger properties + * @regu: Pointer to the struct regulator + * @charger_wq: Work queue for the IRQs and checking HW state + * @check_vbat_work Work for checking vbat threshold to adjust vbus current + * @check_hw_failure_work: Work for checking HW state + * @check_usbchgnotok_work: Work for checking USB charger not ok status + * @kick_wd_work: Work for kicking the charger watchdog in case + * of ABB rev 1.* due to the watchog logic bug + * @ac_work: Work for checking AC charger connection + * @detect_usb_type_work: Work for detecting the USB type connected + * @usb_link_status_work: Work for checking the new USB link status + * @usb_state_changed_work: Work for checking USB state + * @check_main_thermal_prot_work: + * Work for checking Main thermal status + * @check_usb_thermal_prot_work: + * Work for checking USB thermal status + */ +struct ab8500_charger { + struct device *dev; + u8 chip_id; + int max_usb_in_curr; + bool vbus_detected; + bool vbus_detected_start; + bool ac_conn; + bool vddadc_en; + int vbat; + int old_vbat; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct ab8500_charger_platform_data *pdata; + struct ab8500_bm_data *bat; + struct ab8500_charger_event_flags flags; + struct ab8500_charger_usb_state usb_state; + struct ux500_charger ac_chg; + struct ux500_charger usb_chg; + struct ab8500_charger_info ac; + struct ab8500_charger_info usb; + struct regulator *regu; + struct workqueue_struct *charger_wq; + struct delayed_work check_vbat_work; + struct delayed_work check_hw_failure_work; + struct delayed_work check_usbchgnotok_work; + struct delayed_work kick_wd_work; + struct work_struct ac_work; + struct work_struct detect_usb_type_work; + struct work_struct usb_link_status_work; + struct work_struct usb_state_changed_work; + struct work_struct check_main_thermal_prot_work; + struct work_struct check_usb_thermal_prot_work; + struct otg_transceiver *otg; + struct notifier_block nb; +}; + +/* AC properties */ +static enum power_supply_property ab8500_charger_ac_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/* USB properties */ +static enum power_supply_property ab8500_charger_usb_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/** + * ab8500_charger_get_ac_voltage() - get ac charger voltage + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger voltage (on success) + */ +static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->ac.charger_connected) { + vch = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed,\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab8500_charger_ac_cv() - check if the main charger is in CV mode + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger CV mode (on success) else error code + */ +static int ab8500_charger_ac_cv(struct ab8500_charger *di) +{ + u8 val; + int ret = 0; + + /* Only check CV mode if the charger is online */ + if (di->ac.charger_online) { + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return 0; + } + + if (val & MAIN_CH_CV_ON) + ret = 1; + else + ret = 0; + } + + return ret; +} + +/** + * ab8500_charger_get_vbus_voltage() - get vbus voltage + * @di: pointer to the ab8500_charger structure + * + * This function returns the vbus voltage. + * Returns vbus voltage (on success) + */ +static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->usb.charger_connected) { + vch = ab8500_gpadc_convert(di->gpadc, VBUS_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab8500_charger_get_usb_current() - get usb charger current + * @di: pointer to the ab8500_charger structure + * + * This function returns the usb charger current. + * Returns usb current (on success) and error code on failure + */ +static int ab8500_charger_get_usb_current(struct ab8500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->usb.charger_online) { + ich = ab8500_gpadc_convert(di->gpadc, USB_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab8500_charger_get_ac_current() - get ac charger current + * @di: pointer to the ab8500_charger structure + * + * This function returns the ac charger current. + * Returns ac current (on success) and error code on failure. + */ +static int ab8500_charger_get_ac_current(struct ab8500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->ac.charger_online) { + ich = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab8500_charger_usb_cv() - check if the usb charger is in CV mode + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger CV mode (on success) else error code + */ +static int ab8500_charger_usb_cv(struct ab8500_charger *di) +{ + int ret; + u8 val; + + /* Only check CV mode if the charger is online */ + if (di->usb.charger_online) { + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return 0; + } + + if (val & USB_CH_CV_ON) + ret = 1; + else + ret = 0; + } else { + ret = 0; + } + + return ret; +} + +/** + * ab8500_charger_detect_chargers() - Detect the connected chargers + * @di: pointer to the ab8500_charger structure + * + * Returns the type of charger connected. + * For USB it will not mean we can actually charge from it + * but that there is a USB cable connected that we have to + * identify. This is used during startup when we don't get + * interrupts of the charger detection + * + * Returns an integer value, that means, + * NO_PW_CONN no power supply is connected + * AC_PW_CONN if the AC power supply is connected + * USB_PW_CONN if the USB power supply is connected + * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected + */ +static int ab8500_charger_detect_chargers(struct ab8500_charger *di) +{ + int result = NO_PW_CONN; + int ret; + u8 val; + + /* Check for AC charger */ + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + goto out; + } + + if (val & MAIN_CH_DET) + result = AC_PW_CONN; + + /* Check for USB charger */ + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + goto out; + } + + if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100)) + result |= USB_PW_CONN; + + /* + * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts + * will be triggered everytime we enable the VDD ADC supply. + * This will turn off charging for a short while. + * It can be avoided by having the supply on when + * there is a charger connected. Normally the VDD ADC supply + * is enabled everytime a GPADC conversion is triggered. We will + * force it to be enabled from this driver to have + * the GPADC module independant of the AB8500 chargers + */ + if (result == NO_PW_CONN && di->vddadc_en) { + regulator_disable(di->regu); + di->vddadc_en = false; + } else if ((result & AC_PW_CONN || result & USB_PW_CONN) && + !di->vddadc_en) { + regulator_enable(di->regu); + di->vddadc_en = true; + } + + return result; + +out: + if (di->vddadc_en) { + regulator_disable(di->regu); + di->vddadc_en = false; + } + return ret; +} + +/** + * ab8500_charger_max_usb_curr() - get the max curr for the USB type + * @di: pointer to the ab8500_charger structure + * @link_status: the identified USB type + * + * Get the maximum current that is allowed to be drawn from the host + * based on the USB type. + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, + enum ab8500_charger_link_status link_status) +{ + int ret = 0; + + switch (link_status) { + case USB_STAT_STD_HOST_NC: + case USB_STAT_STD_HOST_C_NS: + case USB_STAT_STD_HOST_C_S: + dev_dbg(di->dev, "USB Type - Standard host is " + "detected through USB driver\n"); + ret = -1; + break; + case USB_STAT_HOST_CHG_HS_CHIRP: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + break; + case USB_STAT_HOST_CHG_HS: + case USB_STAT_ACA_RID_C_HS: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P9; + break; + case USB_STAT_ACA_RID_A: + /* + * Dedicated charger level minus maximum current accessory + * can consume (300mA). Closest level is 1100mA + */ + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P1; + break; + case USB_STAT_ACA_RID_B: + /* + * Dedicated charger level minus 120mA (20mA for ACA and + * 100mA for potential accessory). Closest level is 1300mA + */ + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P3; + break; + case USB_STAT_DEDICATED_CHG: + case USB_STAT_HOST_CHG_NM: + case USB_STAT_ACA_RID_C_HS_CHIRP: + case USB_STAT_ACA_RID_C_NM: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5; + break; + case USB_STAT_RESERVED: + /* + * This state is used to indicate that VBUS has dropped below + * the detection level 4 times in a row. This is due to the + * charger output current is set to high making the charger + * voltage collapse. This have to be propagated through to + * chargalg. This is done using the property + * POWER_SUPPLY_PROP_CURRENT_AVG = 1 + */ + di->flags.vbus_collapse = true; + dev_dbg(di->dev, "USB Type - USB_STAT_RESERVED " + "VBUS has collapsed\n"); + ret = -1; + break; + case USB_STAT_HM_IDGND: + case USB_STAT_NOT_CONFIGURED: + case USB_STAT_NOT_VALID_LINK: + dev_err(di->dev, "USB Type - Charging not allowed\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + default: + dev_err(di->dev, "USB Type - Unknown\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + }; + + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", + link_status, di->max_usb_in_curr); + + return ret; +} + +/** + * ab8500_charger_read_usb_type() - read the type of usb connected + * @di: pointer to the ab8500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_read_usb_type(struct ab8500_charger *di) +{ + int ret; + u8 val; + + ret = abx500_get_register_interruptible(di->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINE_STAT_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + /* get the USB type */ + val = (val & AB8500_USB_LINK_STATUS) >> 3; + ret = ab8500_charger_max_usb_curr(di, + (enum ab8500_charger_link_status) val); + + return ret; +} + +/** + * ab8500_charger_detect_usb_type() - get the type of usb connected + * @di: pointer to the ab8500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_detect_usb_type(struct ab8500_charger *di) +{ + int i, ret; + u8 val; + + /* + * On getting the VBUS rising edge detect interrupt there + * is a 250ms delay after which the register UsbLineStatus + * is filled with valid data. + */ + for (i = 0; i < 10; i++) { + msleep(250); + ret = abx500_get_register_interruptible(di->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, + &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINE_STAT_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + /* + * Until the IT source register is read the UsbLineStatus + * register is not updated, hence doing the same + * Revisit this: + */ + + /* get the USB type */ + val = (val & AB8500_USB_LINK_STATUS) >> 3; + if (val) + break; + } + ret = ab8500_charger_max_usb_curr(di, + (enum ab8500_charger_link_status) val); + + return ret; +} + +/* + * This array maps the raw hex value to charger voltage used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_voltage_map[] = { + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3875 , + 3900 , + 3925 , + 3950 , + 3975 , + 4000 , + 4025 , + 4050 , + 4060 , + 4070 , + 4080 , + 4090 , + 4100 , + 4110 , + 4120 , + 4130 , + 4140 , + 4150 , + 4160 , + 4170 , + 4180 , + 4190 , + 4200 , + 4210 , + 4220 , + 4230 , + 4240 , + 4250 , + 4260 , + 4270 , + 4280 , + 4290 , + 4300 , + 4310 , + 4320 , + 4330 , + 4340 , + 4350 , + 4360 , + 4370 , + 4380 , + 4390 , + 4400 , + 4410 , + 4420 , + 4430 , + 4440 , + 4450 , + 4460 , + 4470 , + 4480 , + 4490 , + 4500 , + 4510 , + 4520 , + 4530 , + 4540 , + 4550 , + 4560 , + 4570 , + 4580 , + 4590 , + 4600 , +}; + +/* + * This array maps the raw hex value to charger current used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_current_map[] = { + 100 , + 200 , + 300 , + 400 , + 500 , + 600 , + 700 , + 800 , + 900 , + 1000 , + 1100 , + 1200 , + 1300 , + 1400 , + 1500 , +}; + +/* + * This array maps the raw hex value to VBUS input current used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_vbus_in_curr_map[] = { + USB_CH_IP_CUR_LVL_0P05, + USB_CH_IP_CUR_LVL_0P09, + USB_CH_IP_CUR_LVL_0P19, + USB_CH_IP_CUR_LVL_0P29, + USB_CH_IP_CUR_LVL_0P38, + USB_CH_IP_CUR_LVL_0P45, + USB_CH_IP_CUR_LVL_0P5, + USB_CH_IP_CUR_LVL_0P6, + USB_CH_IP_CUR_LVL_0P7, + USB_CH_IP_CUR_LVL_0P8, + USB_CH_IP_CUR_LVL_0P9, + USB_CH_IP_CUR_LVL_1P0, + USB_CH_IP_CUR_LVL_1P1, + USB_CH_IP_CUR_LVL_1P3, + USB_CH_IP_CUR_LVL_1P4, + USB_CH_IP_CUR_LVL_1P5, +}; + +static int ab8500_voltage_to_regval(int voltage) +{ + int i; + + /* Special case for voltage below 3.5V */ + if (voltage < ab8500_charger_voltage_map[0]) + return LOW_VOLT_REG; + + for (i = 1; i < ARRAY_SIZE(ab8500_charger_voltage_map); i++) { + if (voltage < ab8500_charger_voltage_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_voltage_map) - 1; + if (voltage == ab8500_charger_voltage_map[i]) + return i; + else + return -1; +} + +static int ab8500_current_to_regval(int curr) +{ + int i; + + if (curr < ab8500_charger_current_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab8500_charger_current_map); i++) { + if (curr < ab8500_charger_current_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_current_map) - 1; + if (curr == ab8500_charger_current_map[i]) + return i; + else + return -1; +} + +static int ab8500_vbus_in_curr_to_regval(int curr) +{ + int i; + + if (curr < ab8500_charger_vbus_in_curr_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab8500_charger_vbus_in_curr_map); i++) { + if (curr < ab8500_charger_vbus_in_curr_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_vbus_in_curr_map) - 1; + if (curr == ab8500_charger_vbus_in_curr_map[i]) + return i; + else + return -1; +} + +/** + * ab8500_charger_get_usb_cur() - get usb current + * @di: pointer to the ab8500_charger structre + * + * The usb stack provides the maximum current that can be drawn from + * the standard usb host. This will be in mA. + * This function converts current in mA to a value that can be written + * to the register. Returns -1 if charging is not allowed + */ +static int ab8500_charger_get_usb_cur(struct ab8500_charger *di) +{ + switch (di->usb_state.usb_current) { + case 100: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09; + break; + case 200: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P19; + break; + case 300: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P29; + break; + case 400: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P38; + break; + case 500: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + break; + default: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + return -1; + break; + }; + return 0; +} + +/** + * ab8500_charger_set_vbus_in_curr() - set VBUS input current limit + * @di: pointer to the ab8500_charger structure + * @ich_in: charger input current limit + * + * Sets the current that can be drawn from the USB host + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di, + int ich_in) +{ + int ret; + int input_curr_index; + int min_value; + + /* We should always use to lowest current limit */ + min_value = min(di->bat->chg_params->usb_curr_max, ich_in); + + switch (min_value) { + case 100: + if (di->vbat < VBAT_3700) + min_value = USB_CH_IP_CUR_LVL_0P05; + break; + case 500: + if (di->vbat < VBAT_3700) + min_value = USB_CH_IP_CUR_LVL_0P45; + break; + default: + break; + } + + input_curr_index = ab8500_vbus_in_curr_to_regval(min_value); + if (input_curr_index < 0) { + dev_err(di->dev, "VBUS input current limit too high\n"); + return -ENXIO; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_IPT_CRNTLVL_REG, + input_curr_index << VBUS_IN_CURR_LIM_SHIFT); + if (ret) + dev_err(di->dev, "%s write failed\n", __func__); + + return ret; +} + +/** + * ab8500_charger_led_en() - turn on/off chargign led + * @di: pointer to the ab8500_charger structure + * @on: flag to turn on/off the chargign led + * + * Power ON/OFF charging LED indication + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_led_en(struct ab8500_charger *di, int on) +{ + int ret; + + if (on) { + /* Power ON charging LED indicator, set LED current to 5mA */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_CTRL, + (LED_IND_CUR_5MA | LED_INDICATOR_PWM_ENA)); + if (ret) { + dev_err(di->dev, "Power ON LED failed\n"); + return ret; + } + /* LED indicator PWM duty cycle 252/256 */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_DUTY, + LED_INDICATOR_PWM_DUTY_252_256); + if (ret) { + dev_err(di->dev, "Set LED PWM duty cycle failed\n"); + return ret; + } + } else { + /* Power off charging LED indicator */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_CTRL, + LED_INDICATOR_PWM_DIS); + if (ret) { + dev_err(di->dev, "Power-off LED failed\n"); + return ret; + } + } + + return ret; +} + +/** + * ab8500_charger_ac_en() - enable or disable ac charging + * @di: pointer to the ab8500_charger structure + * @enable: enable/disable flag + * @vset: charging voltage + * @iset: charging current + * + * Enable/Disable AC/Mains charging and turns on/off the charging led + * respectively. + **/ +static int ab8500_charger_ac_en(struct ux500_charger *charger, + int enable, int vset, int iset) +{ + int ret; + int volt_index; + int curr_index; + int input_curr_index; + u8 overshoot = 0; + + struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger); + + if (enable) { + /* Check if AC is connected */ + if (!di->ac.charger_connected) { + dev_err(di->dev, "AC charger not connected\n"); + return -ENXIO; + } + + /* Enable AC charging */ + dev_dbg(di->dev, "Enable AC: %dmV %dmA\n", vset, iset); + + /* Check if the requested voltage or current is valid */ + volt_index = ab8500_voltage_to_regval(vset); + curr_index = ab8500_current_to_regval(iset); + input_curr_index = ab8500_current_to_regval( + di->bat->chg_params->ac_curr_max); + if (volt_index < 0 || curr_index < 0 || input_curr_index < 0) { + dev_err(di->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + /* ChVoltLevel: maximum battery charging voltage */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* MainChInputCurr: current that can be drawn from the charger*/ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_MCH_IPT_CURLVL_REG, + input_curr_index << MAIN_CH_INPUT_CURR_SHIFT); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* ChOutputCurentLevel: protected output current */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* Check if VBAT overshoot control should be enabled */ + if (!di->bat->enable_overshoot) + overshoot = MAIN_CH_NO_OVERSHOOT_ENA_N; + + /* Enable Main Charger */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_MCH_CTRL1, MAIN_CH_ENA | overshoot); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* Power on charging LED indication */ + ret = ab8500_charger_led_en(di, true); + if (ret < 0) + dev_err(di->dev, "failed to enable LED\n"); + + di->ac.charger_online = 1; + } else { + /* Disable AC charging */ + + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + /* + * For ABB revision 1.0 and 1.1 there is a bug in the + * watchdog logic. That means we have to continously + * kick the charger watchdog even when no charger is + * connected. This is only valid once the AC charger + * has been enabled. This is a bug that is not handled + * by the algorithm and the watchdog have to be kicked + * by the charger driver when the AC charger + * is disabled + */ + if (di->ac_conn) { + queue_delayed_work(di->charger_wq, + &di->kick_wd_work, + round_jiffies(WD_KICK_INTERVAL)); + } + + /* + * We can't turn off charging completely + * due to a bug in AB8500 cut1. + * If we do, charging will not start again. + * That is why we set the lowest voltage + * and current possible + */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, CH_VOL_LVL_3P5); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, CH_OP_CUR_LVL_0P1); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + break; + + case AB8500_CUT2P0: + default: + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_MCH_CTRL1, 0); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + break; + } + + ret = ab8500_charger_led_en(di, false); + if (ret < 0) + dev_err(di->dev, "failed to disable LED\n"); + + di->ac.charger_online = 0; + di->ac.wd_expired = false; + dev_dbg(di->dev, "%s Disabled AC charging\n", __func__); + } + power_supply_changed(&di->ac_chg.psy); + + return ret; +} + +/** + * ab8500_charger_usb_en() - enable usb charging + * @di: pointer to the ab8500_charger structure + * @enable: enable/disable flag + * @vset: charging voltage + * @ich_out: charger output current + * + * Enable/Disable USB charging and turns on/off the charging led respectively. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_usb_en(struct ux500_charger *charger, + int enable, int vset, int ich_out) +{ + int ret; + int volt_index; + int curr_index; + u8 overshoot = 0; + + struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger); + + if (enable) { + /* Check if USB is connected */ + if (!di->usb.charger_connected) { + dev_err(di->dev, "USB charger not connected\n"); + return -ENXIO; + } + + /* Enable USB charging */ + dev_dbg(di->dev, "Enable USB: %dmV %dmA\n", vset, ich_out); + + /* Check if the requested voltage or current is valid */ + volt_index = ab8500_voltage_to_regval(vset); + curr_index = ab8500_current_to_regval(ich_out); + if (volt_index < 0 || curr_index < 0) { + dev_err(di->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + /* ChVoltLevel: max voltage upto which battery can be charged */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* USBChInputCurr: current that can be drawn from the usb */ + ret = ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr); + if (ret) { + dev_err(di->dev, "setting USBChInputCurr failed\n"); + return ret; + } + /* ChOutputCurentLevel: protected output current */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* Check if VBAT overshoot control should be enabled */ + if (!di->bat->enable_overshoot) + overshoot = USB_CHG_NO_OVERSHOOT_ENA_N; + + /* Enable USB Charger */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, USB_CH_ENA | overshoot); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* If success power on charging LED indication */ + ret = ab8500_charger_led_en(di, true); + if (ret < 0) + dev_err(di->dev, "failed to enable LED\n"); + + queue_delayed_work(di->charger_wq, &di->check_vbat_work, HZ); + + di->usb.charger_online = 1; + } else { + /* Disable USB charging */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, 0); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + + ret = ab8500_charger_led_en(di, false); + if (ret < 0) + dev_err(di->dev, "failed to disable LED\n"); + + di->usb.charger_online = 0; + di->usb.wd_expired = false; + dev_dbg(di->dev, "%s Disabled USB charging\n", __func__); + + /* Cancel any pending Vbat check work */ + if (delayed_work_pending(&di->check_vbat_work)) + cancel_delayed_work(&di->check_vbat_work); + + } + power_supply_changed(&di->usb_chg.psy); + + return ret; +} + +/** + * ab8500_charger_watchdog_kick() - kick charger watchdog + * @di: pointer to the ab8500_charger structure + * + * Kick charger watchdog + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_watchdog_kick(struct ux500_charger *charger) +{ + int ret; + struct ab8500_charger *di; + + if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS) + di = to_ab8500_charger_ac_device_info(charger); + else if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + return ret; +} + +/** + * ab8500_charger_update_charger_current() - update charger current + * @di: pointer to the ab8500_charger structure + * + * Update the charger output current for the specified charger + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_update_charger_current(struct ux500_charger *charger, + int ich_out) +{ + int ret; + int curr_index; + struct ab8500_charger *di; + + if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS) + di = to_ab8500_charger_ac_device_info(charger); + else if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + curr_index = ab8500_current_to_regval(ich_out); + if (curr_index < 0) { + dev_err(di->dev, + "Charger current too high, " + "charging not started\n"); + return -ENXIO; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* Reset the main and usb drop input current measurement counter */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARGER_CTRL, + 0x1); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + return ret; +} + +static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_charger *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + struct ux500_charger *usb_chg; + + usb_chg = (struct ux500_charger *)data; + psy = &usb_chg->psy; + + di = to_ab8500_charger_usb_device_info(usb_chg); + + ext = dev_get_drvdata(dev); + + /* For all psy where the driver name appears in any supplied_to */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->vbat = ret.intval / 1000; + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_charger_check_vbat_work() - keep vbus current within spec + * @work pointer to the work_struct structure + * + * Due to a asic bug it is necessary to lower the input current to the vbus + * charger when charging with at some specific levels. This issue is only valid + * for below a certain battery voltage. This function makes sure that the + * the allowed current limit isn't exceeded. + */ +static void ab8500_charger_check_vbat_work(struct work_struct *work) +{ + int t = 10; + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_vbat_work.work); + + class_for_each_device(power_supply_class, NULL, + &di->usb_chg.psy, ab8500_charger_get_ext_psy_data); + + /* First run old_vbat is 0. */ + if (di->old_vbat == 0) + di->old_vbat = di->vbat; + + if (!((di->old_vbat <= VBAT_3700 && di->vbat <= VBAT_3700) || + (di->old_vbat > VBAT_3700 && di->vbat > VBAT_3700))) { + dev_dbg(di->dev, "Vbat did cross threshold, curr: %d, new: %d," + " old: %d\n", di->max_usb_in_curr, di->vbat, + di->old_vbat); + ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr); + power_supply_changed(&di->usb_chg.psy); + } + + di->old_vbat = di->vbat; + + /* + * No need to check the battery voltage every second when not close to + * the threshold. + */ + if (di->vbat < (VBAT_3700 + 100) && + (di->vbat > (VBAT_3700 - 100))) + t = 1; + + queue_delayed_work(di->charger_wq, &di->check_vbat_work, t * HZ); +} + +/** + * ab8500_charger_check_hw_failure_work() - check main charger failure + * @work: pointer to the work_struct structure + * + * Work queue function for checking the main charger status + */ +static void ab8500_charger_check_hw_failure_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_hw_failure_work.work); + + /* Check if the status bits for HW failure is still active */ + if (di->flags.mainextchnotok) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_STATUS2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (!(reg_value & MAIN_CH_NOK)) { + di->flags.mainextchnotok = false; + power_supply_changed(&di->ac_chg.psy); + } + } + if (di->flags.vbus_ovv) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, + ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (!(reg_value & VBUS_OVV_TH)) { + di->flags.vbus_ovv = false; + power_supply_changed(&di->usb_chg.psy); + } + } + /* If we still have a failure, schedule a new check */ + if (di->flags.mainextchnotok || di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, round_jiffies(HZ)); + } +} + +/** + * ab8500_charger_kick_watchdog_work() - kick the watchdog + * @work: pointer to the work_struct structure + * + * Work queue function for kicking the charger watchdog. + * + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ +static void ab8500_charger_kick_watchdog_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, kick_wd_work.work); + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + /* Schedule a new watchdog kick */ + queue_delayed_work(di->charger_wq, + &di->kick_wd_work, round_jiffies(WD_KICK_INTERVAL)); +} + +/** + * ab8500_charger_ac_work() - work to get and set main charger status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the main charger status + */ +static void ab8500_charger_ac_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, ac_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if the main charger is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (ret & AC_PW_CONN) { + di->ac.charger_connected = 1; + di->ac_conn = true; + } else { + di->ac.charger_connected = 0; + } + + power_supply_changed(&di->ac_chg.psy); +} + +/** + * ab8500_charger_detect_usb_type_work() - work to detect USB type + * @work: Pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +void ab8500_charger_detect_usb_type_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, detect_usb_type_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + di->vbus_detected = 0; + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } else { + di->vbus_detected = 1; + + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + ret = ab8500_charger_detect_usb_type(di); + if (!ret) { + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } + break; + + case AB8500_CUT2P0: + default: + /* For ABB cut2.0 and onwards we have an IRQ, + * USB_LINK_STATUS that will be triggered when the USB + * link status changes. The exception is USB connected + * during startup. Then we don't get a + * USB_LINK_STATUS IRQ + */ + if (di->vbus_detected_start) { + di->vbus_detected_start = false; + ret = ab8500_charger_detect_usb_type(di); + if (!ret) { + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } + } + break; + } + } +} + +/** + * ab8500_charger_usb_link_status_work() - work to detect USB type + * @work: pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +static void ab8500_charger_usb_link_status_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, usb_link_status_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + di->vbus_detected = 0; + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } else { + di->vbus_detected = 1; + ret = ab8500_charger_read_usb_type(di); + if (!ret) { + /* Update maximum input current */ + ret = ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr); + if (ret) + return; + + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } else if (ret == -ENXIO) { + /* No valid charger type detected */ + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } + } +} + +static void ab8500_charger_usb_state_changed_work(struct work_struct *work) +{ + int ret; + unsigned long flags; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, usb_state_changed_work); + + if (!di->vbus_detected) + return; + + spin_lock_irqsave(&di->usb_state.usb_lock, flags); + di->usb_state.usb_changed = false; + spin_unlock_irqrestore(&di->usb_state.usb_lock, flags); + + /* + * wait for some time until you get updates from the usb stack + * and negotiations are completed + */ + msleep(250); + + if (di->usb_state.usb_changed) + return; + + dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n", + __func__, di->usb_state.state, di->usb_state.usb_current); + + switch (di->usb_state.state) { + case AB8500_BM_USB_STATE_RESET_HS: + case AB8500_BM_USB_STATE_RESET_FS: + case AB8500_BM_USB_STATE_SUSPEND: + case AB8500_BM_USB_STATE_MAX: + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + break; + + case AB8500_BM_USB_STATE_RESUME: + /* + * when suspend->resume there should be delay + * of 1sec for enabling charging + */ + msleep(1000); + /* Intentional fall through */ + case AB8500_BM_USB_STATE_CONFIGURED: + /* + * USB is configured, enable charging with the charging + * input current obtained from USB driver + */ + if (!ab8500_charger_get_usb_cur(di)) { + /* Update maximum input current */ + ret = ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr); + if (ret) + return; + + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } + break; + + default: + break; + }; +} + +/** + * ab8500_charger_check_usbchargernotok_work() - check USB chg not ok status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB charger Not OK status + */ +static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + bool prev_status; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_usbchgnotok_work.work); + + /* Check if the status bit for usbchargernotok is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + prev_status = di->flags.usbchargernotok; + + if (reg_value & VBUS_CH_NOK) { + di->flags.usbchargernotok = true; + /* Check again in 1sec */ + queue_delayed_work(di->charger_wq, + &di->check_usbchgnotok_work, HZ); + } else { + di->flags.usbchargernotok = false; + di->flags.vbus_collapse = false; + } + + if (prev_status != di->flags.usbchargernotok) + power_supply_changed(&di->usb_chg.psy); +} + +/** + * ab8500_charger_check_main_thermal_prot_work() - check main thermal status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the Main thermal prot status + */ +static void ab8500_charger_check_main_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_main_thermal_prot_work); + + /* Check if the status bit for main_thermal_prot is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_STATUS2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (reg_value & MAIN_CH_TH_PROT) + di->flags.main_thermal_prot = true; + else + di->flags.main_thermal_prot = false; + + power_supply_changed(&di->ac_chg.psy); +} + +/** + * ab8500_charger_check_usb_thermal_prot_work() - check usb thermal status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB thermal prot status + */ +static void ab8500_charger_check_usb_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_usb_thermal_prot_work); + + /* Check if the status bit for usb_thermal_prot is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (reg_value & USB_CH_TH_PROT) + di->flags.usb_thermal_prot = true; + else + di->flags.usb_thermal_prot = false; + + power_supply_changed(&di->usb_chg.psy); +} + +/** + * ab8500_charger_mainchunplugdet_handler() - main charger unplugged + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchunplugdet_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger unplugged\n"); + queue_work(di->charger_wq, &di->ac_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchplugdet_handler() - main charger plugged + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger plugged\n"); + queue_work(di->charger_wq, &di->ac_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainextchnotok_handler() - main charger not ok + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainextchnotok_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger not ok\n"); + di->flags.mainextchnotok = true; + power_supply_changed(&di->ac_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchthprotr_handler() - Die temp is above main charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchthprotr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp above Main charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_main_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchthprotf_handler() - Die temp is below main charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchthprotf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp ok for Main charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_main_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusdetf_handler() - VBUS falling detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusdetf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "VBUS falling detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusdetr_handler() - VBUS rising detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusdetr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + di->vbus_detected = true; + dev_dbg(di->dev, "VBUS rising detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usblinkstatus_handler() - USB link status has changed + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usblinkstatus_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "USB link status changed\n"); + + queue_work(di->charger_wq, &di->usb_link_status_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchthprotr_handler() - Die temp is above usb charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchthprotr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp above USB charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchthprotf_handler() - Die temp is below usb charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchthprotf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp ok for USB charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchargernotokr_handler() - USB charger not ok detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchargernotokr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Not allowed USB charger detected\n"); + queue_delayed_work(di->charger_wq, &di->check_usbchgnotok_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_chwdexp_handler() - Charger watchdog expired + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_chwdexp_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Charger watchdog expired\n"); + + /* + * The charger that was online when the watchdog expired + * needs to be restarted for charging to start again + */ + if (di->ac.charger_online) { + di->ac.wd_expired = true; + power_supply_changed(&di->ac_chg.psy); + } + if (di->usb.charger_online) { + di->usb.wd_expired = true; + power_supply_changed(&di->usb_chg.psy); + } + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusovv_handler() - VBUS overvoltage detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusovv_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "VBUS overvoltage detected\n"); + di->flags.vbus_ovv = true; + power_supply_changed(&di->usb_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_ac_get_property() - get the ac/mains properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the ac/mains + * properties by reading the sysfs files. + * AC/Mains properties are online, present and voltage. + * online: ac/mains charging is in progress or not + * present: presence of the ac/mains + * voltage: AC/Mains voltage + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_charger *di; + + di = to_ab8500_charger_ac_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (di->flags.mainextchnotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (di->ac.wd_expired || di->usb.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (di->flags.main_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->ac.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->ac.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + di->ac.charger_voltage = ab8500_charger_get_ac_voltage(di); + val->intval = di->ac.charger_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* + * This property is used to indicate when CV mode is entered + * for the AC charger + */ + di->ac.cv_active = ab8500_charger_ac_cv(di); + val->intval = di->ac.cv_active; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ab8500_charger_get_ac_current(di) * 1000; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * ab8500_charger_usb_get_property() - get the usb properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the usb + * properties by reading the sysfs files. + * USB properties are online, present and voltage. + * online: usb charging is in progress or not + * present: presence of the usb + * voltage: vbus voltage + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_charger *di; + + di = to_ab8500_charger_usb_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (di->flags.usbchargernotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (di->ac.wd_expired || di->usb.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (di->flags.usb_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (di->flags.vbus_ovv) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->usb.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->usb.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + di->usb.charger_voltage = ab8500_charger_get_vbus_voltage(di); + val->intval = di->usb.charger_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* + * This property is used to indicate when CV mode is entered + * for the USB charger + */ + di->usb.cv_active = ab8500_charger_usb_cv(di); + val->intval = di->usb.cv_active; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ab8500_charger_get_usb_current(di) * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + /* + * This property is used to indicate when VBUS has collapsed + * due to too high output current from the USB charger + */ + if (di->flags.vbus_collapse) + val->intval = 1; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * ab8500_charger_init_hw_registers() - Set up charger related registers + * @di: pointer to the ab8500_charger structure + * + * Set up charger OVV, watchdog and maximum voltage registers as well as + * charging of the backup battery + */ +static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) +{ + int ret = 0; + + /* Setup maximum charger current and voltage for ABB cut2.0 */ + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + break; + case AB8500_CUT2P0: + default: + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_VOLT_LVL_MAX_REG, CH_VOL_LVL_4P6); + if (ret) { + dev_err(di->dev, + "failed to set CH_VOLT_LVL_MAX_REG\n"); + goto out; + } + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_MAX_REG, CH_OP_CUR_LVL_1P6); + if (ret) { + dev_err(di->dev, + "failed to set CH_OPT_CRNTLVL_MAX_REG\n"); + goto out; + } + + break; + } + + /* VBUS OVV set to 6.3V and enable automatic current limitiation */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL2_REG, + VBUS_OVV_SELECT_6P3V | VBUS_AUTO_IN_CURR_LIM_ENA); + if (ret) { + dev_err(di->dev, "failed to set VBUS OVV\n"); + goto out; + } + + /* Enable main watchdog in OTP */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_OTP_EMUL, AB8500_OTP_CONF_15, OTP_ENABLE_WD); + if (ret) { + dev_err(di->dev, "failed to enable main WD in OTP\n"); + goto out; + } + + /* Enable main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_ENA); + if (ret) { + dev_err(di->dev, "faile to enable main watchdog\n"); + goto out; + } + + /* + * Due to internal synchronisation, Enable and Kick watchdog bits + * cannot be enabled in a single write. + * A minimum delay of 2*32 kHz period (62.5µs) must be inserted + * between writing Enable then Kick bits. + */ + udelay(63); + + /* Kick main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, + (MAIN_WDOG_ENA | MAIN_WDOG_KICK)); + if (ret) { + dev_err(di->dev, "failed to kick main watchdog\n"); + goto out; + } + + /* Disable main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_DIS); + if (ret) { + dev_err(di->dev, "failed to disable main watchdog\n"); + goto out; + } + + /* Set watchdog timeout */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_WD_TIMER_REG, WD_TIMER); + if (ret) { + dev_err(di->dev, "failed to set charger watchdog timeout\n"); + goto out; + } + + /* Backup battery voltage and current */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_RTC, + AB8500_RTC_BACKUP_CHG_REG, + di->bat->bkup_bat_v | + di->bat->bkup_bat_i); + if (ret) { + dev_err(di->dev, "failed to setup backup battery charging\n"); + goto out; + } + + /* Enable backup battery charging */ + abx500_mask_and_set_register_interruptible(di->dev, + AB8500_RTC, AB8500_RTC_CTRL_REG, + RTC_BUP_CH_ENA, RTC_BUP_CH_ENA); + if (ret < 0) + dev_err(di->dev, "%s mask and set failed\n", __func__); + +out: + return ret; +} + +/* + * ab8500 charger driver interrupts and their respective isr + */ +static struct ab8500_charger_interrupts ab8500_charger_irq[] = { + {"MAIN_CH_UNPLUG_DET", ab8500_charger_mainchunplugdet_handler}, + {"MAIN_CHARGE_PLUG_DET", ab8500_charger_mainchplugdet_handler}, + {"MAIN_EXT_CH_NOT_OK", ab8500_charger_mainextchnotok_handler}, + {"MAIN_CH_TH_PROT_R", ab8500_charger_mainchthprotr_handler}, + {"MAIN_CH_TH_PROT_F", ab8500_charger_mainchthprotf_handler}, + {"VBUS_DET_F", ab8500_charger_vbusdetf_handler}, + {"VBUS_DET_R", ab8500_charger_vbusdetr_handler}, + {"USB_LINK_STATUS", ab8500_charger_usblinkstatus_handler}, + {"USB_CH_TH_PROT_R", ab8500_charger_usbchthprotr_handler}, + {"USB_CH_TH_PROT_F", ab8500_charger_usbchthprotf_handler}, + {"USB_CHARGER_NOT_OKR", ab8500_charger_usbchargernotokr_handler}, + {"VBUS_OVV", ab8500_charger_vbusovv_handler}, + {"CH_WD_EXP", ab8500_charger_chwdexp_handler}, +}; + +static int ab8500_charger_usb_notifier_call(struct notifier_block *nb, + unsigned long event, void *power) +{ + struct ab8500_charger *di = + container_of(nb, struct ab8500_charger, nb); + enum ab8500_usb_state bm_usb_state; + unsigned mA = *((unsigned *)power); + + /* TODO: State is fabricate here. See if charger really needs USB + * state or if mA is enough + */ + if ((di->usb_state.usb_current == 2) && (mA > 2)) + bm_usb_state = AB8500_BM_USB_STATE_RESUME; + else if (mA == 0) + bm_usb_state = AB8500_BM_USB_STATE_RESET_HS; + else if (mA == 2) + bm_usb_state = AB8500_BM_USB_STATE_SUSPEND; + else if (mA >= 8) /* 8, 100, 500 */ + bm_usb_state = AB8500_BM_USB_STATE_CONFIGURED; + else /* Should never occur */ + bm_usb_state = AB8500_BM_USB_STATE_RESET_FS; + + dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n", + __func__, bm_usb_state, mA); + + spin_lock(&di->usb_state.usb_lock); + di->usb_state.usb_changed = true; + spin_unlock(&di->usb_state.usb_lock); + + di->usb_state.state = bm_usb_state; + di->usb_state.usb_current = mA; + + queue_work(di->charger_wq, &di->usb_state_changed_work); + + return NOTIFY_OK; +} + +#if defined(CONFIG_PM) +static int ab8500_charger_resume(struct platform_device *pdev) +{ + int ret; + struct ab8500_charger *di = platform_get_drvdata(pdev); + + /* + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ + if (di->ac_conn && (di->chip_id == AB8500_CUT1P0 || + di->chip_id == AB8500_CUT1P1)) { + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + /* If not already pending start a new timer */ + if (!delayed_work_pending( + &di->kick_wd_work)) { + queue_delayed_work(di->charger_wq, &di->kick_wd_work, + round_jiffies(WD_KICK_INTERVAL)); + } + } + + /* If we still have a HW failure, schedule a new check */ + if (di->flags.mainextchnotok || di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, 0); + } + + return 0; +} + +static int ab8500_charger_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_charger *di = platform_get_drvdata(pdev); + + /* Cancel any pending HW failure check */ + if (delayed_work_pending(&di->check_hw_failure_work)) + cancel_delayed_work(&di->check_hw_failure_work); + + return 0; +} +#else +#define ab8500_charger_suspend NULL +#define ab8500_charger_resume NULL +#endif + +static int __devexit ab8500_charger_remove(struct platform_device *pdev) +{ + struct ab8500_charger *di = platform_get_drvdata(pdev); + int i, irq, ret; + + /* Disable AC charging */ + ab8500_charger_ac_en(&di->ac_chg, false, 0, 0); + + /* Disable USB charging */ + ab8500_charger_usb_en(&di->usb_chg, false, 0, 0); + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + free_irq(irq, di); + } + + /* disable the regulator */ + regulator_put(di->regu); + + /* Backup battery voltage and current disable */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0); + if (ret < 0) + dev_err(di->dev, "%s mask and set failed\n", __func__); + + otg_unregister_notifier(di->otg, &di->nb); + otg_put_transceiver(di->otg); + + /* Delete the work queue */ + destroy_workqueue(di->charger_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->usb_chg.psy); + power_supply_unregister(&di->ac_chg.psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab8500_charger_probe(struct platform_device *pdev) +{ + int irq, i, charger_status, ret = 0; + struct ab8500_platform_data *plat; + + struct ab8500_charger *di = + kzalloc(sizeof(struct ab8500_charger), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get(); + + /* initialize lock */ + spin_lock_init(&di->usb_state.usb_lock); + + plat = dev_get_platdata(di->parent->dev); + + /* get charger specific platform data */ + if (!plat->charger) { + dev_err(di->dev, "no charger platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->pdata = plat->charger; + + /* get battery specific platform data */ + if (!plat->battery) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->bat = plat->battery; + + /* AC supply */ + /* power_supply base class */ + di->ac_chg.psy.name = "ab8500_ac"; + di->ac_chg.psy.type = POWER_SUPPLY_TYPE_MAINS; + di->ac_chg.psy.properties = ab8500_charger_ac_props; + di->ac_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_ac_props); + di->ac_chg.psy.get_property = ab8500_charger_ac_get_property; + di->ac_chg.psy.supplied_to = di->pdata->supplied_to; + di->ac_chg.psy.num_supplicants = di->pdata->num_supplicants; + /* ux500_charger sub-class */ + di->ac_chg.ops.enable = &ab8500_charger_ac_en; + di->ac_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; + di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current; + di->ac_chg.max_out_volt = ab8500_charger_voltage_map[ + ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; + di->ac_chg.max_out_curr = ab8500_charger_current_map[ + ARRAY_SIZE(ab8500_charger_current_map) - 1]; + + /* USB supply */ + /* power_supply base class */ + di->usb_chg.psy.name = "ab8500_usb"; + di->usb_chg.psy.type = POWER_SUPPLY_TYPE_USB; + di->usb_chg.psy.properties = ab8500_charger_usb_props; + di->usb_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_usb_props); + di->usb_chg.psy.get_property = ab8500_charger_usb_get_property; + di->usb_chg.psy.supplied_to = di->pdata->supplied_to; + di->usb_chg.psy.num_supplicants = di->pdata->num_supplicants; + /* ux500_charger sub-class */ + di->usb_chg.ops.enable = &ab8500_charger_usb_en; + di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; + di->usb_chg.ops.update_curr = &ab8500_charger_update_charger_current; + di->usb_chg.max_out_volt = ab8500_charger_voltage_map[ + ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; + di->usb_chg.max_out_curr = ab8500_charger_current_map[ + ARRAY_SIZE(ab8500_charger_current_map) - 1]; + + + /* Create a work queue for the charger */ + di->charger_wq = + create_singlethread_workqueue("ab8500_charger_wq"); + if (di->charger_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for HW failure check */ + INIT_DELAYED_WORK_DEFERRABLE(&di->check_hw_failure_work, + ab8500_charger_check_hw_failure_work); + INIT_DELAYED_WORK_DEFERRABLE(&di->check_usbchgnotok_work, + ab8500_charger_check_usbchargernotok_work); + + /* + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ + INIT_DELAYED_WORK_DEFERRABLE(&di->kick_wd_work, + ab8500_charger_kick_watchdog_work); + + INIT_DELAYED_WORK_DEFERRABLE(&di->check_vbat_work, + ab8500_charger_check_vbat_work); + + /* Init work for charger detection */ + INIT_WORK(&di->usb_link_status_work, + ab8500_charger_usb_link_status_work); + INIT_WORK(&di->ac_work, ab8500_charger_ac_work); + INIT_WORK(&di->detect_usb_type_work, + ab8500_charger_detect_usb_type_work); + + INIT_WORK(&di->usb_state_changed_work, + ab8500_charger_usb_state_changed_work); + + /* Init work for checking HW status */ + INIT_WORK(&di->check_main_thermal_prot_work, + ab8500_charger_check_main_thermal_prot_work); + INIT_WORK(&di->check_usb_thermal_prot_work, + ab8500_charger_check_usb_thermal_prot_work); + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(di->dev); + if (ret < 0) { + dev_err(di->dev, "failed to get chip ID\n"); + goto free_charger_wq; + } + di->chip_id = ret; + dev_dbg(di->dev, "AB8500 CID is: 0x%02x\n", di->chip_id); + + /* + * VDD ADC supply needs to be enabled from this driver when there + * is a charger connected to avoid erroneous BTEMP_HIGH/LOW + * interrupts during charging + */ + di->regu = regulator_get(di->dev, "vddadc"); + if (IS_ERR(di->regu)) { + ret = PTR_ERR(di->regu); + dev_err(di->dev, "failed to get vddadc regulator\n"); + goto free_charger_wq; + } + + + /* Initialize OVV, and other registers */ + ret = ab8500_charger_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize ABB registers\n"); + goto free_regulator; + } + + /* Register AC charger class */ + ret = power_supply_register(di->dev, &di->ac_chg.psy); + if (ret) { + dev_err(di->dev, "failed to register AC charger\n"); + goto free_regulator; + } + + /* Register USB charger class */ + ret = power_supply_register(di->dev, &di->usb_chg.psy); + if (ret) { + dev_err(di->dev, "failed to register USB charger\n"); + goto free_ac; + } + + di->otg = otg_get_transceiver(); + if (!di->otg) { + dev_err(di->dev, "failed to get otg transceiver\n"); + ret = -EINVAL; + goto free_usb; + } + di->nb.notifier_call = ab8500_charger_usb_notifier_call; + ret = otg_register_notifier(di->otg, &di->nb); + if (ret) { + dev_err(di->dev, "failed to register otg notifier\n"); + goto put_otg_transceiver; + } + + /* Identify the connected charger types during startup */ + charger_status = ab8500_charger_detect_chargers(di); + if (charger_status & AC_PW_CONN) { + di->ac.charger_connected = 1; + di->ac_conn = true; + power_supply_changed(&di->ac_chg.psy); + } + + if (charger_status & USB_PW_CONN) { + dev_dbg(di->dev, "VBUS Detect during startup\n"); + di->vbus_detected = true; + di->vbus_detected_start = true; + queue_work(di->charger_wq, + &di->detect_usb_type_work); + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_charger_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_charger_irq[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_charger_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_charger_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + return ret; + +free_irq: + otg_unregister_notifier(di->otg, &di->nb); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + free_irq(irq, di); + } +put_otg_transceiver: + otg_put_transceiver(di->otg); +free_usb: + power_supply_unregister(&di->usb_chg.psy); +free_ac: + power_supply_unregister(&di->ac_chg.psy); +free_regulator: + regulator_put(di->regu); +free_charger_wq: + destroy_workqueue(di->charger_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_charger_driver = { + .probe = ab8500_charger_probe, + .remove = __devexit_p(ab8500_charger_remove), + .suspend = ab8500_charger_suspend, + .resume = ab8500_charger_resume, + .driver = { + .name = "ab8500-charger", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_charger_init(void) +{ + return platform_driver_register(&ab8500_charger_driver); +} + +static void __exit ab8500_charger_exit(void) +{ + platform_driver_unregister(&ab8500_charger_driver); +} + +subsys_initcall_sync(ab8500_charger_init); +module_exit(ab8500_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); +MODULE_ALIAS("platform:ab8500-charger"); +MODULE_DESCRIPTION("AB8500 charger management driver"); diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c new file mode 100644 index 00000000000..e192893764a --- /dev/null +++ b/drivers/power/ab8500_fg.c @@ -0,0 +1,2316 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * Main and Back-up battery management driver. + * + * Note: Backup battery management is required in case of Li-Ion battery and not + * for capacitive battery. HREF boards have capacitive battery and hence backup + * battery management is not used and the supported code is available in this + * driver. + * + * License Terms: GNU General Public License v2 + * Author: Johan Palsson <johan.palsson@stericsson.com> + * Author: Karl Komierowski <karl.komierowski@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/kobject.h> +#include <linux/mfd/ab8500.h> +#include <linux/mfd/abx500.h> +#include <linux/slab.h> +#include <linux/mfd/ab8500/bm.h> +#include <linux/delay.h> +#include <linux/mfd/ab8500/gpadc.h> +#include <linux/mfd/abx500.h> +#include <linux/time.h> + +#define MILLI_TO_MICRO 1000 +#define FG_LSB_IN_MA 1627 +#define QLSB_NANO_AMP_HOURS_X10 1129 + +#define SEC_TO_SAMPLE(S) (S * 4) + +#define NBR_AVG_SAMPLES 20 + +#define LOW_BAT_CHECK_INTERVAL (2 * HZ) + +#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */ + +#define interpolate(x, x1, y1, x2, y2) \ + ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1)))); + +#define to_ab8500_fg_device_info(x) container_of((x), \ + struct ab8500_fg, fg_psy); + +/** + * struct ab8500_fg_interrupts - ab8500 fg interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_fg_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +enum ab8500_fg_discharge_state { + AB8500_FG_DISCHARGE_INIT, + AB8500_FG_DISCHARGE_INITMEASURING, + AB8500_FG_DISCHARGE_INIT_RECOVERY, + AB8500_FG_DISCHARGE_RECOVERY, + AB8500_FG_DISCHARGE_READOUT, + AB8500_FG_DISCHARGE_WAKEUP, +}; + +static char *discharge_state[] = { + "DISCHARGE_INIT", + "DISCHARGE_INITMEASURING", + "DISCHARGE_INIT_RECOVERY", + "DISCHARGE_RECOVERY", + "DISCHARGE_READOUT", + "DISCHARGE_WAKEUP", +}; + +enum ab8500_fg_charge_state { + AB8500_FG_CHARGE_INIT, + AB8500_FG_CHARGE_READOUT, +}; + +static char *charge_state[] = { + "CHARGE_INIT", + "CHARGE_READOUT", +}; + +enum ab8500_fg_calibration_state { + AB8500_FG_CALIB_INIT, + AB8500_FG_CALIB_WAIT, + AB8500_FG_CALIB_END, +}; + +struct ab8500_fg_avg_cap { + int avg; + int samples[NBR_AVG_SAMPLES]; + __kernel_time_t time_stamps[NBR_AVG_SAMPLES]; + int pos; + int nbr_samples; + int sum; +}; + +struct ab8500_fg_battery_capacity { + int max_mah_design; + int max_mah; + int mah; + int permille; + int level; + int prev_mah; + int prev_percent; + int prev_level; +}; + +struct ab8500_fg_flags { + bool fg_enabled; + bool conv_done; + bool charging; + bool fully_charged; + bool low_bat_delay; + bool low_bat; + bool bat_ovv; + bool batt_unknown; + bool calibrate; +}; + +struct inst_curr_result_list { + struct list_head list; + int *result; +}; + +/** + * struct ab8500_fg - ab8500 FG device information + * @dev: Pointer to the structure device + * @node: a list of AB8500 FGs, hence prepared for reentrance + * @vbat: Battery voltage in mV + * @vbat_nom: Nominal battery voltage in mV + * @inst_curr: Instantenous battery current in mA + * @avg_curr: Average battery current in mA + * @fg_samples: Number of samples used in the FG accumulation + * @accu_charge: Accumulated charge from the last conversion + * @recovery_cnt: Counter for recovery mode + * @high_curr_cnt: Counter for high current mode + * @init_cnt: Counter for init mode + * @recovery_needed: Indicate if recovery is needed + * @high_curr_mode: Indicate if we're in high current mode + * @init_capacity: Indicate if initial capacity measuring should be done + * @inst_curr_mip: Indicate if 'instant' current measurement is in progress + * AB8500 does not support real instant current + * readings. The best we can do is sample over + * 250ms. + * @inst_curr_lock: Control access to inst curr wait queues and result lists + * @inst_curr_wq: Work queue for running 'instant' current measurements + * @fg_inst_curr_work: Work to measure 'instant' current + * @result_wq: Wait queue for blocking 'instant' current clients + * @cpw_a_wq: Physical wait queue A for concurrent inst curr clients + * @cpw_b_wq: Physical wait queue B for concurrent inst curr clients + * @cpw_next_wq: Logical next wait queue for concurrent inst curr clients + * @cpw_this_wq: Logical this wait queue for concurrent inst curr clients + * @inst_curr_result: Result register for blocking instant current clients + * @result_a_list: Physical result list A for concurrent inst curr clients + * @result_b_list: Physical result list B for concurrent inst curr clients + * @next_result_list: Logical next results for concurrent inst curr clients + * @this_result_list: Logical this results for concurrent inst curr clients + * @calib_state State during offset calibration + * @discharge_state: Current discharge state + * @charge_state: Current charge state + * @flags: Structure for information about events triggered + * @bat_cap: Structure for battery capacity specific parameters + * @avg_cap: Average capacity filter + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @pdata: Pointer to the ab8500_fg platform data + * @bat: Pointer to the ab8500_bm platform data + * @fg_psy: Structure that holds the FG specific battery properties + * @fg_wq: Work queue for running the FG algorithm + * @fg_periodic_work: Work to run the FG algorithm periodically + * @fg_low_bat_work: Work to check low bat condition + * @fg_reinit_work Work used to reset and reinitialise the FG algorithm + * @fg_work: Work to run the FG algorithm instantly + * @fg_acc_cur_work: Work to read the FG accumulator + * @cc_lock: Mutex for locking the CC + * @fg_kobject: Structure of type kobject + */ +struct ab8500_fg { + struct device *dev; + struct list_head node; + int vbat; + int vbat_nom; + int inst_curr; + int avg_curr; + int fg_samples; + int accu_charge; + int recovery_cnt; + int high_curr_cnt; + int init_cnt; + bool recovery_needed; + bool high_curr_mode; + bool init_capacity; + bool inst_curr_mip; + spinlock_t inst_curr_lock; + struct workqueue_struct *inst_curr_wq; + struct delayed_work fg_inst_curr_work; + wait_queue_head_t result_wq; + wait_queue_head_t cpw_a_wq; + wait_queue_head_t cpw_b_wq; + wait_queue_head_t *cpw_next_wq; + wait_queue_head_t *cpw_this_wq; + int inst_curr_result; + struct list_head result_a_list; + struct list_head result_b_list; + struct list_head *next_result_list; + struct list_head *this_result_list; + enum ab8500_fg_calibration_state calib_state; + enum ab8500_fg_discharge_state discharge_state; + enum ab8500_fg_charge_state charge_state; + struct ab8500_fg_flags flags; + struct ab8500_fg_battery_capacity bat_cap; + struct ab8500_fg_avg_cap avg_cap; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct ab8500_fg_platform_data *pdata; + struct ab8500_bm_data *bat; + struct power_supply fg_psy; + struct workqueue_struct *fg_wq; + struct delayed_work fg_periodic_work; + struct delayed_work fg_low_bat_work; + struct delayed_work fg_reinit_work; + struct work_struct fg_work; + struct work_struct fg_acc_cur_work; + struct mutex cc_lock; + struct kobject fg_kobject; +}; +static LIST_HEAD(ab8500_fg_list); + +/** + * ab8500_fg_get() - returns a reference to the primary AB8500 fuel gauge + * (i.e. the first fuel gauge in the instance list) + */ +struct ab8500_fg *ab8500_fg_get(void) +{ + struct ab8500_fg *fg; + + if (list_empty(&ab8500_fg_list)) + return NULL; + + fg = list_first_entry(&ab8500_fg_list, struct ab8500_fg, node); + return fg; +} + +/* Main battery properties */ +static enum power_supply_property ab8500_fg_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, +}; + +/* + * This array maps the raw hex value to lowbat voltage used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_fg_lowbat_voltage_map[] = { + 2300 , + 2325 , + 2350 , + 2375 , + 2400 , + 2425 , + 2450 , + 2475 , + 2500 , + 2525 , + 2550 , + 2575 , + 2600 , + 2625 , + 2650 , + 2675 , + 2700 , + 2725 , + 2750 , + 2775 , + 2800 , + 2825 , + 2850 , + 2875 , + 2900 , + 2925 , + 2950 , + 2975 , + 3000 , + 3025 , + 3050 , + 3075 , + 3100 , + 3125 , + 3150 , + 3175 , + 3200 , + 3225 , + 3250 , + 3275 , + 3300 , + 3325 , + 3350 , + 3375 , + 3400 , + 3425 , + 3450 , + 3475 , + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3850 , +}; + +static u8 ab8500_volt_to_regval(int voltage) +{ + int i; + + if (voltage < ab8500_fg_lowbat_voltage_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab8500_fg_lowbat_voltage_map); i++) { + if (voltage < ab8500_fg_lowbat_voltage_map[i]) + return (u8) i - 1; + } + + /* If not captured above, return index of last element */ + return (u8) ARRAY_SIZE(ab8500_fg_lowbat_voltage_map) - 1; +} + +/** + * ab8500_fg_is_low_curr() - Low or high current mode + * @di: pointer to the ab8500_fg structure + * @curr: the current to base or our decision on + * + * Low current mode if the current consumption is below a certain threshold + */ +static int ab8500_fg_is_low_curr(struct ab8500_fg *di, int curr) +{ + /* + * We want to know if we're in low current mode + */ + if (curr > -di->bat->fg_params->high_curr_threshold) + return true; + else + return false; +} + +/** + * ab8500_fg_add_cap_sample() - Add capacity to average filter + * @di: pointer to the ab8500_fg structure + * @sample: the capacity in mAh to add to the filter + * + * A capacity is added to the filter and a new mean capacity is calculated and + * returned + */ +static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample) +{ + struct timespec ts; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + do { + avg->sum += sample - avg->samples[avg->pos]; + avg->samples[avg->pos] = sample; + avg->time_stamps[avg->pos] = ts.tv_sec; + avg->pos++; + + if (avg->pos == NBR_AVG_SAMPLES) + avg->pos = 0; + + if (avg->nbr_samples < NBR_AVG_SAMPLES) + avg->nbr_samples++; + + /* + * Check the time stamp for each sample. If too old, + * replace with latest sample + */ + } while (ts.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]); + + avg->avg = avg->sum / avg->nbr_samples; + + return avg->avg; +} + +/** + * ab8500_fg_clear_cap_samples() - Clear average filter + * @di: pointer to the ab8500_fg structure + * + * The capacity filter is is reset to zero. + */ +static void ab8500_fg_clear_cap_samples(struct ab8500_fg *di) +{ + int i; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + avg->pos = 0; + avg->nbr_samples = 0; + avg->sum = 0; + avg->avg = 0; + + for (i = 0; i < NBR_AVG_SAMPLES; i++) { + avg->samples[i] = 0; + avg->time_stamps[i] = 0; + } +} + +/** + * ab8500_fg_fill_cap_sample() - Fill average filter + * @di: pointer to the ab8500_fg structure + * @sample: the capacity in mAh to fill the filter with + * + * The capacity filter is filled with a capacity in mAh + */ +static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample) +{ + int i; + struct timespec ts; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + for (i = 0; i < NBR_AVG_SAMPLES; i++) { + avg->samples[i] = sample; + avg->time_stamps[i] = ts.tv_sec; + } + + avg->pos = 0; + avg->nbr_samples = NBR_AVG_SAMPLES; + avg->sum = sample * NBR_AVG_SAMPLES; + avg->avg = sample; +} + +/** + * ab8500_fg_coulomb_counter() - enable coulomb counter + * @di: pointer to the ab8500_fg structure + * @enable: enable/disable + * + * Enable/Disable coulomb counter. + * On failure returns negative value. + */ +static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable) +{ + int ret = 0; + mutex_lock(&di->cc_lock); + if (enable) { + /* To be able to reprogram the number of samples, we have to + * first stop the CC and then enable it again */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0x00); + if (ret) + goto cc_err; + + /* Program the samples */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, + di->fg_samples); + if (ret) + goto cc_err; + + /* Start the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, + (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); + if (ret) + goto cc_err; + + di->flags.fg_enabled = true; + } else { + /* Clear any pending read requests */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0); + if (ret) + goto cc_err; + + /* Stop the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0); + if (ret) + goto cc_err; + + di->flags.fg_enabled = false; + + } + dev_dbg(di->dev, " CC enabled: %d Samples: %d\n", + enable, di->fg_samples); + + mutex_unlock(&di->cc_lock); + + return ret; +cc_err: + dev_err(di->dev, "%s Enabling coulomb counter failed\n", __func__); + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab8500_fg_inst_curr_nonblocking() - battery instantaneous current + * @di: pointer to the ab8500_fg structure + * @local_result: pointer to result location, updated after measurement is + * completed ~250ms after this function returns + * + * Returns error code + */ +int ab8500_fg_inst_curr_nonblocking(struct ab8500_fg *di, int *local_result) +{ + DEFINE_WAIT(wait); + + /* + * caller needs to do some other work at + * the same time as current measurement + */ + wait_queue_head_t *cpw_my_wq; + struct inst_curr_result_list *new = + kmalloc(sizeof(struct inst_curr_result_list), GFP_KERNEL); + if (!new) + return -ENOMEM; + new->result = local_result; + *local_result = INVALID_CURRENT; + INIT_LIST_HEAD(&new->list); + spin_lock(&di->inst_curr_lock); + list_add(&new->list, di->next_result_list); + cpw_my_wq = di->cpw_next_wq; + add_wait_queue(cpw_my_wq, &wait); + /* queue_work may schedule */ + queue_delayed_work(di->inst_curr_wq, &di->fg_inst_curr_work, 1); + set_current_state(TASK_UNINTERRUPTIBLE); + spin_unlock(&di->inst_curr_lock); + schedule(); + finish_wait(cpw_my_wq, &wait); + return 0; +} + +/** + * ab8500_fg_inst_curr_blocking() - battery instantaneous current + * @di: pointer to the ab8500_fg structure + * + * Returns battery instantenous current(on success) else error code + */ +int ab8500_fg_inst_curr_blocking(struct ab8500_fg *di) +{ + DEFINE_WAIT(wait); + + /* caller will wait for the next available result */ + spin_lock(&di->inst_curr_lock); + add_wait_queue(&di->result_wq, &wait); + if (!di->inst_curr_mip) + /* queue_work may schedule */ + queue_delayed_work(di->inst_curr_wq, &di->fg_inst_curr_work, 1); + set_current_state(TASK_UNINTERRUPTIBLE); + spin_unlock(&di->inst_curr_lock); + schedule(); + finish_wait(&di->result_wq, &wait); + return di->inst_curr_result; +} + +/** + * ab8500_fg_inst_curr_work() - take an 'instant' battery current reading + * @work: pointer to the work_struct structure + * + * AB8500 does not provide instant current readings, the best we can do is + * average over 250ms. + */ +static void ab8500_fg_inst_curr_work(struct work_struct *work) +{ + struct list_head *temp_result_list; + wait_queue_head_t *cpw_temp_wq; + u8 low, high, reg_val; + static int val; + int ret = 0; + bool fg_off = false; + + struct ab8500_fg *di = container_of(work, + struct ab8500_fg, fg_inst_curr_work.work); + + spin_lock(&di->inst_curr_lock); + temp_result_list = di->next_result_list; + cpw_temp_wq = di->cpw_next_wq; + di->next_result_list = di->this_result_list; + di->cpw_next_wq = di->cpw_this_wq; + di->this_result_list = temp_result_list; + di->cpw_this_wq = cpw_temp_wq; + + di->inst_curr_mip = true; + spin_unlock(&di->inst_curr_lock); + + mutex_lock(&di->cc_lock); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, ®_val); + if (ret < 0) + goto inst_curr_err1; + + if (!(reg_val & CC_PWR_UP_ENA)) { + dev_dbg(di->dev, "%s Enable FG\n", __func__); + fg_off = true; + + /* Program the samples */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, + SEC_TO_SAMPLE(10)); + if (ret) + goto inst_curr_err1; + + /* Start the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, + (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); + if (ret) + goto inst_curr_err1; + } + + /* Reset counter and Read request */ + ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_CTRL_REG, (RESET_ACCU | READ_REQ)); + if (ret) + goto inst_curr_err1; + + wake_up(di->cpw_this_wq); + + /* + * Since there is no interrupt for this, just wait for 250ms + * 250ms is one sample conversion time with 32.768 Khz RTC clock + */ + msleep(250); + + /* Read CC Sample conversion value Low and high */ + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_SMPL_CNVL_REG, &low); + if (ret < 0) + goto inst_curr_err2; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_SMPL_CNVH_REG, &high); + if (ret < 0) + goto inst_curr_err2; + + /* + * negative value for Discharging + * convert 2's compliment into decimal + */ + if (high & 0x10) + val = (low | (high << 8) | 0xFFFFE000); + else + val = (low | (high << 8)); + + /* + * Convert to unit value in mA + * Full scale input voltage is + * 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA + * resistance is in mOhm + */ + val = ((val * 66660) / (4096 * di->bat->fg_res)); + + if (fg_off) { + dev_dbg(di->dev, "%s Disable FG\n", __func__); + + /* Clear any pending read requests */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0); + if (ret) + goto inst_curr_err3; + + /* Stop the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0); + if (ret) + goto inst_curr_err3; + } + +finished: + mutex_unlock(&di->cc_lock); + + spin_lock(&di->inst_curr_lock); + di->inst_curr_result = val; + + while (!list_empty(di->this_result_list)) { + struct inst_curr_result_list *this = list_first_entry( + di->this_result_list, + struct inst_curr_result_list, + list); + *(this->result) = val; + list_del(&this->list); + kfree(this); + } + di->inst_curr_mip = false; + wake_up(&di->result_wq); + spin_unlock(&di->inst_curr_lock); + return; + +inst_curr_err1: + wake_up(di->cpw_this_wq); +inst_curr_err2: + val = 0; +inst_curr_err3: + dev_err(di->dev, "%s Get instanst current failed\n", __func__); + goto finished; +} + +/** + * ab8500_fg_acc_cur_work() - average battery current + * @work: pointer to the work_struct structure + * + * Updated the average battery current obtained from the + * coulomb counter. + */ +static void ab8500_fg_acc_cur_work(struct work_struct *work) +{ + int val; + int ret; + u8 low, med, high; + + struct ab8500_fg *di = container_of(work, + struct ab8500_fg, fg_acc_cur_work); + + mutex_lock(&di->cc_lock); + ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_CTRL, RD_NCONV_ACCU_REQ); + if (ret) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_LOW, &low); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_MED, &med); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_HIGH, &high); + if (ret < 0) + goto exit; + + /* Check for sign bit in case of negative value, 2's compliment */ + if (high & 0x10) + val = (low | (med << 8) | (high << 16) | 0xFFE00000); + else + val = (low | (med << 8) | (high << 16)); + + di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10)/10000; + + di->avg_curr = (val * FG_LSB_IN_MA) / (di->fg_samples * 1000); + di->flags.conv_done = true; + + mutex_unlock(&di->cc_lock); + + queue_work(di->fg_wq, &di->fg_work); + + return; +exit: + dev_err(di->dev, + "Failed to read or write gas gauge registers\n"); + mutex_unlock(&di->cc_lock); + queue_work(di->fg_wq, &di->fg_work); +} + +/** + * ab8500_fg_bat_voltage() - get battery voltage + * @di: pointer to the ab8500_fg structure + * + * Returns battery voltage(on success) else error code + */ +static int ab8500_fg_bat_voltage(struct ab8500_fg *di) +{ + int vbat; + static int prev; + + vbat = ab8500_gpadc_convert(di->gpadc, MAIN_BAT_V); + if (vbat < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value\n", + __func__); + return prev; + } + + prev = vbat; + return vbat; +} + +/** + * ab8500_fg_volt_to_capacity() - Voltage based capacity + * @di: pointer to the ab8500_fg structure + * @voltage: The voltage to convert to a capacity + * + * Returns battery capacity in per mille based on voltage + */ +static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage) +{ + int i, tbl_size; + struct v_to_cap *tbl; + int cap = 0; + + tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl, + tbl_size = di->bat->bat_type[di->bat->batt_id].n_v_cap_tbl_elements; + + for (i = 0; i < tbl_size; ++i) { + if (voltage > tbl[i].voltage) + break; + } + + if ((i > 0) && (i < tbl_size)) { + cap = interpolate(voltage, + tbl[i].voltage, + tbl[i].capacity * 10, + tbl[i-1].voltage, + tbl[i-1].capacity * 10); + } else if (i == 0) { + cap = 1000; + } else { + cap = 0; + } + + dev_dbg(di->dev, "%s Vbat: %d, Cap: %d per mille", + __func__, voltage, cap); + + return cap; +} + +/** + * ab8500_fg_uncomp_volt_to_capacity() - Uncompensated voltage based capacity + * @di: pointer to the ab8500_fg structure + * + * Returns battery capacity based on battery voltage that is not compensated + * for the voltage drop due to the load + */ +static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di) +{ + di->vbat = ab8500_fg_bat_voltage(di); + return ab8500_fg_volt_to_capacity(di, di->vbat); +} + +/** + * ab8500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity + * @di: pointer to the ab8500_fg structure + * + * Returns battery capacity based on battery voltage that is load compensated + * for the voltage drop + */ +static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di) +{ + int vbat_comp; + + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + di->vbat = ab8500_fg_bat_voltage(di); + + /* Use Ohms law to get the load compensated voltage */ + vbat_comp = di->vbat - (di->inst_curr * + di->bat->bat_type[di->bat->batt_id].battery_resistance) / 1000; + + dev_dbg(di->dev, "%s Measured Vbat: %dmV,Compensated Vbat %dmV, " + "R: %dmOhm, Current: %dmA\n", + __func__, + di->vbat, + vbat_comp, + di->bat->bat_type[di->bat->batt_id].battery_resistance, + di->inst_curr); + + return ab8500_fg_volt_to_capacity(di, vbat_comp); +} + +/** + * ab8500_fg_convert_mah_to_permille() - Capacity in mAh to permille + * @di: pointer to the ab8500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in permille + */ +static int ab8500_fg_convert_mah_to_permille(struct ab8500_fg *di, int cap_mah) +{ + return (cap_mah * 1000) / di->bat_cap.max_mah_design; +} + +/** + * ab8500_fg_convert_permille_to_mah() - Capacity in permille to mAh + * @di: pointer to the ab8500_fg structure + * @cap_pm: capacity in permille + * + * Converts capacity in permille to capacity in mAh + */ +static int ab8500_fg_convert_permille_to_mah(struct ab8500_fg *di, int cap_pm) +{ + return cap_pm * di->bat_cap.max_mah_design / 1000; +} + +/** + * ab8500_fg_convert_mah_to_uwh() - Capacity in mAh to uWh + * @di: pointer to the ab8500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in uWh + */ +static int ab8500_fg_convert_mah_to_uwh(struct ab8500_fg *di, int cap_mah) +{ + u64 div_res; + u32 div_rem; + + div_res = ((u64) cap_mah) * ((u64) di->vbat_nom); + div_rem = do_div(div_res, 1000); + + /* Make sure to round upwards if necessary */ + if (div_rem >= 1000 / 2) + div_res++; + + return (int) div_res; +} + +/** + * ab8500_fg_calc_cap_charging() - Calculate remaining capacity while charging + * @di: pointer to the ab8500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. The filter is filled with this capacity + */ +static int ab8500_fg_calc_cap_charging(struct ab8500_fg *di) +{ + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + + /* + * We force capacity to 100% as long as the algorithm + * reports that it's full. + */ + if (di->bat_cap.mah >= di->bat_cap.max_mah_design || + di->flags.fully_charged) + di->bat_cap.mah = di->bat_cap.max_mah_design; + + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + /* We need to update battery voltage and inst current when charging */ + di->vbat = ab8500_fg_bat_voltage(di); + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage + * @di: pointer to the ab8500_fg structure + * @comp: if voltage should be load compensated before capacity calc + * + * Return the capacity in mAh based on the battery voltage. The voltage can + * either be load compensated or not. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di, bool comp) +{ + int permille, mah; + + if (comp) + permille = ab8500_fg_load_comp_volt_to_capacity(di); + else + permille = ab8500_fg_uncomp_volt_to_capacity(di); + + mah = ab8500_fg_convert_permille_to_mah(di, permille); + + di->bat_cap.mah = ab8500_fg_add_cap_sample(di, mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_calc_cap_discharge_fg() - Capacity in discharge with FG + * @di: pointer to the ab8500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab8500_fg_calc_cap_discharge_fg(struct ab8500_fg *di) +{ + int permille_volt, permille; + + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + + if (di->bat_cap.mah >= di->bat_cap.max_mah_design) + di->bat_cap.mah = di->bat_cap.max_mah_design; + + /* + * Check against voltage based capacity. It can not be lower + * than what the uncompensated voltage says + */ + permille = ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + permille_volt = ab8500_fg_uncomp_volt_to_capacity(di); + + if (permille < permille_volt) { + di->bat_cap.permille = permille_volt; + di->bat_cap.mah = ab8500_fg_convert_permille_to_mah(di, + di->bat_cap.permille); + + dev_dbg(di->dev, "%s voltage based: perm %d perm_volt %d\n", + __func__, + permille, + permille_volt); + + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + } else { + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + } + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_capacity_level() - Get the battery capacity level + * @di: pointer to the ab8500_fg structure + * + * Get the battery capacity level based on the capacity in percent + */ +static int ab8500_fg_capacity_level(struct ab8500_fg *di) +{ + int ret, percent; + + percent = di->bat_cap.permille / 10; + + if (percent <= di->bat->cap_levels->critical || + di->flags.low_bat) + ret = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else if (percent <= di->bat->cap_levels->low) + ret = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (percent <= di->bat->cap_levels->normal) + ret = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (percent <= di->bat->cap_levels->high) + ret = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + else + ret = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + + return ret; +} + +/** + * ab8500_fg_check_capacity_limits() - Check if capacity has changed + * @di: pointer to the ab8500_fg structure + * @init: capacity is allowed to go up in init mode + * + * Check if capacity or capacity limit has changed and notify the system + * about it using the power_supply framework + */ +static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init) +{ + bool changed = false; + + di->bat_cap.level = ab8500_fg_capacity_level(di); + + if (di->bat_cap.level != di->bat_cap.prev_level) { + /* + * We do not allow reported capacity level to go up + * unless we're charging or if we're in init + */ + if (!(!di->flags.charging && di->bat_cap.level > + di->bat_cap.prev_level) || init) { + dev_dbg(di->dev, "level changed from %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + di->bat_cap.prev_level = di->bat_cap.level; + changed = true; + } else { + dev_dbg(di->dev, "level not allowed to go up " + "since no charger is connected: %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + } + } + + /* + * If we have received the LOW_BAT IRQ, set capacity to 0 to initiate + * shutdown + */ + if (di->flags.low_bat) { + dev_dbg(di->dev, "Battery low, set capacity to 0\n"); + di->bat_cap.prev_percent = 0; + di->bat_cap.permille = 0; + di->bat_cap.prev_mah = 0; + di->bat_cap.mah = 0; + changed = true; + } else if (di->bat_cap.prev_percent != di->bat_cap.permille / 10) { + if (di->bat_cap.permille / 10 == 0) { + /* + * We will not report 0% unless we've got + * the LOW_BAT IRQ, no matter what the FG + * algorithm says. + */ + di->bat_cap.prev_percent = 1; + di->bat_cap.permille = 1; + di->bat_cap.prev_mah = 1; + di->bat_cap.mah = 1; + + changed = true; + } else if (!(!di->flags.charging && + (di->bat_cap.permille / 10) > + di->bat_cap.prev_percent) || init) { + /* + * We do not allow reported capacity to go up + * unless we're charging or if we're in init + */ + dev_dbg(di->dev, + "capacity changed from %d to %d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.permille / 10, + di->bat_cap.permille); + di->bat_cap.prev_percent = di->bat_cap.permille / 10; + di->bat_cap.prev_mah = di->bat_cap.mah; + + changed = true; + } else { + dev_dbg(di->dev, "capacity not allowed to go up since " + "no charger is connected: %d to %d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.permille / 10, + di->bat_cap.permille); + } + } + + if (changed) { + power_supply_changed(&di->fg_psy); + if (di->flags.fully_charged) { + dev_dbg(di->dev, "Full, notifying..: %d\n", + di->flags.fully_charged); + sysfs_notify(&di->fg_kobject, NULL, "charge_full"); + } + } +} + +static void ab8500_fg_charge_state_to(struct ab8500_fg *di, + enum ab8500_fg_charge_state new_state) +{ + dev_dbg(di->dev, "Charge state from %d [%s] to %d [%s]\n", + di->charge_state, + charge_state[di->charge_state], + new_state, + charge_state[new_state]); + + di->charge_state = new_state; +} + +static void ab8500_fg_discharge_state_to(struct ab8500_fg *di, + enum ab8500_fg_charge_state new_state) +{ + dev_dbg(di->dev, "Disharge state from %d [%s] to %d [%s]\n", + di->discharge_state, + discharge_state[di->discharge_state], + new_state, + discharge_state[new_state]); + + di->discharge_state = new_state; +} + +/** + * ab8500_fg_algorithm_charging() - FG algorithm for when charging + * @di: pointer to the ab8500_fg structure + * + * Battery capacity calculation state machine for when we're charging + */ +static void ab8500_fg_algorithm_charging(struct ab8500_fg *di) +{ + /* + * If we change to discharge mode + * we should start with recovery + */ + if (di->discharge_state != AB8500_FG_DISCHARGE_INIT_RECOVERY) + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_INIT_RECOVERY); + + switch (di->charge_state) { + case AB8500_FG_CHARGE_INIT: + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_charging); + + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_READOUT); + + break; + + case AB8500_FG_CHARGE_READOUT: + /* + * Read the FG and calculate the new capacity + */ + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + ab8500_fg_calc_cap_charging(di); + + break; + + default: + break; + } + + /* Check capacity limits */ + ab8500_fg_check_capacity_limits(di, false); +} + +/** + * ab8500_fg_algorithm_discharging() - FG algorithm for when discharging + * @di: pointer to the ab8500_fg structure + * + * Battery capacity calculation state machine for when we're discharging + */ +static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) +{ + int sleep_time; + + /* If we change to charge mode we should start with init */ + if (di->charge_state != AB8500_FG_CHARGE_INIT) + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + + switch (di->discharge_state) { + case AB8500_FG_DISCHARGE_INIT: + /* We use the FG IRQ to work on */ + di->init_cnt = 0; + di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_INITMEASURING); + + /* Intentional fallthrough */ + case AB8500_FG_DISCHARGE_INITMEASURING: + /* + * Discard a number of samples during startup. + * After that, use compensated voltage for a few + * samples to get an initial capacity. + * Then go to READOUT + */ + sleep_time = di->bat->fg_params->init_timer; + + /* Discard the first [x] seconds */ + if (di->init_cnt > + di->bat->fg_params->init_discard_time) { + + ab8500_fg_calc_cap_discharge_voltage(di, true); + + ab8500_fg_check_capacity_limits(di, true); + } + + di->init_cnt += sleep_time; + if (di->init_cnt > + di->bat->fg_params->init_total_time) { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + } + + break; + + case AB8500_FG_DISCHARGE_INIT_RECOVERY: + di->recovery_cnt = 0; + di->recovery_needed = true; + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_RECOVERY); + + /* Intentional fallthrough */ + + case AB8500_FG_DISCHARGE_RECOVERY: + sleep_time = di->bat->fg_params->recovery_sleep_timer; + + /* + * We should check the power consumption + * If low, go to READOUT (after x min) or + * RECOVERY_SLEEP if time left. + * If high, go to READOUT + */ + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + + if (ab8500_fg_is_low_curr(di, di->inst_curr)) { + if (di->recovery_cnt > + di->bat->fg_params->recovery_total_time) { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + di->recovery_needed = false; + } else { + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, + sleep_time * HZ); + } + di->recovery_cnt += sleep_time; + } else { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + } + + break; + + case AB8500_FG_DISCHARGE_READOUT: + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + + if (ab8500_fg_is_low_curr(di, di->inst_curr)) { + /* Detect mode change */ + if (di->high_curr_mode) { + di->high_curr_mode = false; + di->high_curr_cnt = 0; + } + + if (di->recovery_needed) { + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_RECOVERY); + + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, 0); + + break; + } + + ab8500_fg_calc_cap_discharge_voltage(di, true); + } else { + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + /* Detect mode change */ + if (!di->high_curr_mode) { + di->high_curr_mode = true; + di->high_curr_cnt = 0; + } + + di->high_curr_cnt += + di->bat->fg_params->accu_high_curr; + if (di->high_curr_cnt > + di->bat->fg_params->high_curr_time) + di->recovery_needed = true; + + ab8500_fg_calc_cap_discharge_fg(di); + } + + ab8500_fg_check_capacity_limits(di, false); + + break; + + case AB8500_FG_DISCHARGE_WAKEUP: + ab8500_fg_coulomb_counter(di, true); + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + + ab8500_fg_calc_cap_discharge_voltage(di, true); + + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + /* Re-program number of samples set above */ + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_READOUT); + + ab8500_fg_check_capacity_limits(di, false); + + break; + + default: + break; + } +} + +/** + * ab8500_fg_algorithm_calibrate() - Internal columb counter offset calibration + * @di: pointer to the ab8500_fg structure + * + */ +static void ab8500_fg_algorithm_calibrate(struct ab8500_fg *di) +{ + int ret; + + switch (di->calib_state) { + case AB8500_FG_CALIB_INIT: + dev_dbg(di->dev, "Calibration ongoing...\n"); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + CC_INT_CAL_N_AVG_MASK, CC_INT_CAL_SAMPLES_8); + if (ret < 0) + goto err; + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + CC_INTAVGOFFSET_ENA, CC_INTAVGOFFSET_ENA); + if (ret < 0) + goto err; + di->calib_state = AB8500_FG_CALIB_WAIT; + break; + case AB8500_FG_CALIB_END: + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + CC_MUXOFFSET, CC_MUXOFFSET); + if (ret < 0) + goto err; + di->flags.calibrate = false; + dev_dbg(di->dev, "Calibration done...\n"); + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + break; + case AB8500_FG_CALIB_WAIT: + dev_dbg(di->dev, "Calibration WFI\n"); + default: + break; + } + return; +err: + /* Something went wrong, don't calibrate then */ + dev_err(di->dev, "failed to calibrate the CC\n"); + di->flags.calibrate = false; + di->calib_state = AB8500_FG_CALIB_INIT; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); +} + +/** + * ab8500_fg_algorithm() - Entry point for the FG algorithm + * @di: pointer to the ab8500_fg structure + * + * Entry point for the battery capacity calculation state machine + */ +static void ab8500_fg_algorithm(struct ab8500_fg *di) +{ + if (di->flags.calibrate) + ab8500_fg_algorithm_calibrate(di); + else { + if (di->flags.charging) + ab8500_fg_algorithm_charging(di); + else + ab8500_fg_algorithm_discharging(di); + } + + dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d " + "%d %d %d %d %d %d %d\n", + di->bat_cap.max_mah_design, + di->bat_cap.mah, + di->bat_cap.permille, + di->bat_cap.level, + di->bat_cap.prev_mah, + di->bat_cap.prev_percent, + di->bat_cap.prev_level, + di->vbat, + di->inst_curr, + di->avg_curr, + di->accu_charge, + di->flags.charging, + di->charge_state, + di->discharge_state, + di->high_curr_mode, + di->recovery_needed); +} + +/** + * ab8500_fg_periodic_work() - Run the FG state machine periodically + * @work: pointer to the work_struct structure + * + * Work queue function for periodic work + */ +static void ab8500_fg_periodic_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_periodic_work.work); + + if (di->init_capacity) { + /* A dummy read that will return 0 */ + di->inst_curr = ab8500_fg_inst_curr_blocking(di); + /* Get an initial capacity calculation */ + ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_check_capacity_limits(di, true); + di->init_capacity = false; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + } else + ab8500_fg_algorithm(di); +} + +/** + * ab8500_fg_low_bat_work() - Check LOW_BAT condition + * @work: pointer to the work_struct structure + * + * Work queue function for checking the LOW_BAT condition + */ +static void ab8500_fg_low_bat_work(struct work_struct *work) +{ + int vbat; + + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_low_bat_work.work); + + vbat = ab8500_fg_bat_voltage(di); + + /* Check if LOW_BAT still fulfilled */ + if (vbat < di->bat->fg_params->lowbat_threshold) { + di->flags.low_bat = true; + dev_warn(di->dev, "Battery voltage still LOW\n"); + + /* + * We need to re-schedule this check to be able to detect + * if the voltage increases again during charging + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } else { + di->flags.low_bat = false; + dev_warn(di->dev, "Battery voltage OK again\n"); + } + + /* This is needed to dispatch LOW_BAT */ + ab8500_fg_check_capacity_limits(di, false); + + /* Set this flag to check if LOW_BAT IRQ still occurs */ + di->flags.low_bat_delay = false; +} + +/** + * ab8500_fg_instant_work() - Run the FG state machine instantly + * @work: pointer to the work_struct structure + * + * Work queue function for instant work + */ +static void ab8500_fg_instant_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, fg_work); + + ab8500_fg_algorithm(di); +} + +/** + * ab8500_fg_cc_convend_handler() - isr to get battery avg current. + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_cc_int_calib_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + di->calib_state = AB8500_FG_CALIB_END; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + return IRQ_HANDLED; +} + +/** + * ab8500_fg_cc_convend_handler() - isr to get battery avg current. + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_cc_convend_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + queue_work(di->fg_wq, &di->fg_acc_cur_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_fg_batt_ovv_handler() - Battery OVV occured + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_batt_ovv_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + dev_dbg(di->dev, "Battery OVV\n"); + di->flags.bat_ovv = true; + + power_supply_changed(&di->fg_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_fg_lowbatf_handler() - Battery voltage is below LOW threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_lowbatf_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + if (!di->flags.low_bat_delay) { + dev_warn(di->dev, "Battery voltage is below LOW threshold\n"); + di->flags.low_bat_delay = true; + /* + * Start a timer to check LOW_BAT again after some time + * This is done to avoid shutdown on single voltage dips + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } + return IRQ_HANDLED; +} + +/** + * ab8500_fg_get_property() - get the fg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * fg properties by reading the sysfs files. + * voltage_now: battery voltage + * current_now: battery instant current + * current_avg: battery average current + * charge_full_design: capacity where battery is considered full + * charge_now: battery capacity in nAh + * capacity: capacity in percent + * capacity_level: capacity level + * + * Returns error code in case of failure else 0 on success + */ +static int ab8500_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + /* + * If battery is identified as unknown and charging of unknown + * batteries is disabled, we always report 100% capacity and + * capacity level UNKNOWN, since we can't calculate + * remaining capacity + */ + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (di->flags.bat_ovv) + val->intval = 47500000; + else + val->intval = di->vbat * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->inst_curr * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = di->avg_curr * 1000; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah_design); + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + else + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.prev_mah); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = di->bat_cap.max_mah_design; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = di->bat_cap.max_mah; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = di->bat_cap.max_mah; + else + val->intval = di->bat_cap.prev_mah; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = 100; + else + val->intval = di->bat_cap.prev_percent; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + else + val->intval = di->bat_cap.prev_level; + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_fg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_fg_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + switch (ret.intval) { + case POWER_SUPPLY_STATUS_UNKNOWN: + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (!di->flags.charging) + break; + di->flags.charging = false; + di->flags.fully_charged = false; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_FULL: + if (di->flags.fully_charged) + break; + di->flags.fully_charged = true; + /* Save current capacity as maximum */ + di->bat_cap.max_mah = di->bat_cap.mah; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (di->flags.charging) + break; + di->flags.charging = true; + di->flags.fully_charged = false; + queue_work(di->fg_wq, &di->fg_work); + break; + }; + default: + break; + }; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (ret.intval) + di->flags.batt_unknown = false; + else + di->flags.batt_unknown = true; + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_fg_init_hw_registers() - Set up FG related registers + * @di: pointer to the ab8500_fg structure + * + * Set up battery OVV, low battery voltage registers + */ +static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) +{ + int ret; + + /* Set up VBAT OVV register */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_BATT_OVV, + (BATT_OVV_ENA | BATT_OVV_TH_4P75)); + if (ret) { + dev_err(di->dev, "failed to set BATT_OVV\n"); + goto out; + } + + /* Low Battery Voltage */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_LOW_BAT_REG, + ab8500_volt_to_regval( + di->bat->fg_params->lowbat_threshold) << 1 | + LOW_BAT_ENABLE); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + goto out; + } + +out: + return ret; +} + +/** + * ab8500_fg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void ab8500_fg_external_power_changed(struct power_supply *psy) +{ + struct ab8500_fg *di = to_ab8500_fg_device_info(psy); + + class_for_each_device(power_supply_class, NULL, + &di->fg_psy, ab8500_fg_get_ext_psy_data); +} + +/** + * abab8500_fg_reinit_work() - work to reset the FG algorithm + * @work: pointer to the work_struct structure + * + * Used to reset the current battery capacity to be able to + * retrigger a new voltage base capacity calculation. For + * test and verification purpose. + */ +static void ab8500_fg_reinit_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_reinit_work.work); + + if (di->flags.calibrate == false) { + dev_dbg(di->dev, "Resetting FG state machine to init.\n"); + ab8500_fg_clear_cap_samples(di); + ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + + } else { + dev_err(di->dev, "Residual offset calibration ongoing " + "retrying..\n"); + /* Wait one second until next try*/ + queue_delayed_work(di->fg_wq, &di->fg_reinit_work, + round_jiffies(1)); + } +} + +/** + * ab8500_fg_reinit() - forces FG algorithm to reinitialize with current values + * + * This function can be used to force the FG algorithm to recalculate a new + * voltage based battery capacity. + */ +void ab8500_fg_reinit(void) +{ + struct ab8500_fg *di = ab8500_fg_get(); + /* User won't be notified if a null pointer returned. */ + if (di != NULL) + queue_delayed_work(di->fg_wq, &di->fg_reinit_work, 0); +} + +/* Exposure to the sysfs interface */ + +struct ab8500_fg_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct ab8500_fg *, char *); + ssize_t (*store)(struct ab8500_fg *, const char *, size_t); +}; + +static ssize_t charge_full_show(struct ab8500_fg *di, char *buf) +{ + return sprintf(buf, "%d\n", di->bat_cap.max_mah); +} + +static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf, + size_t count) +{ + unsigned long charge_full; + ssize_t ret = -EINVAL; + + ret = strict_strtoul(buf, 10, &charge_full); + + dev_dbg(di->dev, "Ret %d charge_full %lu", ret, charge_full); + + if (!ret) { + di->bat_cap.max_mah = (int) charge_full; + ret = count; + } + return ret; +} +static struct ab8500_fg_sysfs_entry charge_full_attr = + __ATTR(charge_full, 0644, charge_full_show, charge_full_store); + +static ssize_t +ab8500_fg_show(struct kobject *kobj, struct attribute *attr, char *buf) +{ + struct ab8500_fg_sysfs_entry *entry; + struct ab8500_fg *di; + + entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr); + di = container_of(kobj, struct ab8500_fg, fg_kobject); + + if (!entry->show) + return -EIO; + + return entry->show(di, buf); +} +static ssize_t +ab8500_fg_store(struct kobject *kobj, struct attribute *attr, const char *buf, + size_t count) +{ + struct ab8500_fg_sysfs_entry *entry; + struct ab8500_fg *di; + + entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr); + di = container_of(kobj, struct ab8500_fg, fg_kobject); + + if (!entry->store) + return -EIO; + + return entry->store(di, buf, count); +} + +const struct sysfs_ops ab8500_fg_sysfs_ops = { + .show = ab8500_fg_show, + .store = ab8500_fg_store, +}; + +static struct attribute *ab8500_fg_attrs[] = { + &charge_full_attr.attr, + NULL, +}; + +static struct kobj_type ab8500_fg_ktype = { + .sysfs_ops = &ab8500_fg_sysfs_ops, + .default_attrs = ab8500_fg_attrs, +}; + +/** + * ab8500_chargalg_sysfs_exit() - de-init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function removes the entry in sysfs. + */ +static void ab8500_fg_sysfs_exit(struct ab8500_fg *di) +{ + kobject_del(&di->fg_kobject); +} + +/** + * ab8500_chargalg_sysfs_init() - init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function adds an entry in sysfs. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_fg_sysfs_init(struct ab8500_fg *di) +{ + int ret = 0; + + ret = kobject_init_and_add(&di->fg_kobject, + &ab8500_fg_ktype, + NULL, "ab8500_fg"); + if (ret < 0) + dev_err(di->dev, "failed to create sysfs entry\n"); + + return ret; +} +/* Exposure to the sysfs interface <<END>> */ + +#if defined(CONFIG_PM) +static int ab8500_fg_resume(struct platform_device *pdev) +{ + struct ab8500_fg *di = platform_get_drvdata(pdev); + + /* + * Change state if we're not charging. If we're charging we will wake + * up on the FG IRQ + */ + if (!di->flags.charging) { + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_WAKEUP); + queue_work(di->fg_wq, &di->fg_work); + } + + return 0; +} + +static int ab8500_fg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_fg *di = platform_get_drvdata(pdev); + + flush_delayed_work(&di->fg_periodic_work); + + /* + * If the FG is enabled we will disable it before going to suspend + * only if we're not charging + */ + if (di->flags.fg_enabled && !di->flags.charging) + ab8500_fg_coulomb_counter(di, false); + + return 0; +} +#else +#define ab8500_fg_suspend NULL +#define ab8500_fg_resume NULL +#endif + +static int __devexit ab8500_fg_remove(struct platform_device *pdev) +{ + int ret = 0; + struct ab8500_fg *di = platform_get_drvdata(pdev); + + list_del(&di->node); + + /* Disable coulomb counter */ + ret = ab8500_fg_coulomb_counter(di, false); + if (ret) + dev_err(di->dev, "failed to disable coulomb counter\n"); + + flush_workqueue(di->inst_curr_wq); + destroy_workqueue(di->inst_curr_wq); + destroy_workqueue(di->fg_wq); + ab8500_fg_sysfs_exit(di); + + flush_scheduled_work(); + power_supply_unregister(&di->fg_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + return ret; +} + +/* ab8500 fg driver interrupts and their respective isr */ +static struct ab8500_fg_interrupts ab8500_fg_irq[] = { + {"NCONV_ACCU", ab8500_fg_cc_convend_handler}, + {"BATT_OVV", ab8500_fg_batt_ovv_handler}, + {"LOW_BAT_F", ab8500_fg_lowbatf_handler}, + {"CC_INT_CALIB", ab8500_fg_cc_int_calib_handler}, +}; + +static int __devinit ab8500_fg_probe(struct platform_device *pdev) +{ + int i, irq; + struct ab8500_platform_data *plat; + int ret = 0; + + struct ab8500_fg *di = + kzalloc(sizeof(struct ab8500_fg), GFP_KERNEL); + if (!di) + return -ENOMEM; + + mutex_init(&di->cc_lock); + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get(); + + plat = dev_get_platdata(di->parent->dev); + + /* get fg specific platform data */ + if (!plat->fg) { + dev_err(di->dev, "no fg platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->pdata = plat->fg; + + /* get battery specific platform data */ + if (!plat->battery) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->bat = plat->battery; + + di->fg_psy.name = "ab8500_fg"; + di->fg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->fg_psy.properties = ab8500_fg_props; + di->fg_psy.num_properties = ARRAY_SIZE(ab8500_fg_props); + di->fg_psy.get_property = ab8500_fg_get_property; + di->fg_psy.supplied_to = di->pdata->supplied_to; + di->fg_psy.num_supplicants = di->pdata->num_supplicants; + di->fg_psy.external_power_changed = ab8500_fg_external_power_changed; + + di->bat_cap.max_mah_design = MILLI_TO_MICRO * + di->bat->bat_type[di->bat->batt_id].charge_full_design; + + di->bat_cap.max_mah = di->bat_cap.max_mah_design; + + di->vbat_nom = di->bat->bat_type[di->bat->batt_id].nominal_voltage; + + di->init_capacity = true; + + di->inst_curr_mip = false; + spin_lock_init(&di->inst_curr_lock); + init_waitqueue_head(&di->result_wq); + init_waitqueue_head(&di->cpw_a_wq); + init_waitqueue_head(&di->cpw_b_wq); + di->cpw_next_wq = &di->cpw_a_wq; + di->cpw_this_wq = &di->cpw_b_wq; + di->inst_curr_result = 0; + INIT_LIST_HEAD(&di->result_a_list); + INIT_LIST_HEAD(&di->result_b_list); + di->next_result_list = &di->result_a_list; + di->this_result_list = &di->result_b_list; + + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); + + /* Create a work queue for running the FG algorithm */ + di->fg_wq = create_singlethread_workqueue("ab8500_fg_wq"); + if (di->fg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + di->inst_curr_wq = create_singlethread_workqueue("ab8500_inst_curr_wq"); + if (di->inst_curr_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_fg_wq; + } + + /* Init work for running the instant current measurment */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_inst_curr_work, + ab8500_fg_inst_curr_work); + + /* Init work for running the fg algorithm instantly */ + INIT_WORK(&di->fg_work, ab8500_fg_instant_work); + + /* Init work for getting the battery accumulated current */ + INIT_WORK(&di->fg_acc_cur_work, ab8500_fg_acc_cur_work); + + /* Init work for reinitialising the fg algorithm */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_reinit_work, + ab8500_fg_reinit_work); + + /* Work delayed Queue to run the state machine */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_periodic_work, + ab8500_fg_periodic_work); + + /* Work to check low battery condition */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_low_bat_work, + ab8500_fg_low_bat_work); + + /* Initialize OVV, and other registers */ + ret = ab8500_fg_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize registers\n"); + goto free_inst_curr_wq; + } + + /* Consider battery unknown until we're informed otherwise */ + di->flags.batt_unknown = true; + + /* Register FG power supply class */ + ret = power_supply_register(di->dev, &di->fg_psy); + if (ret) { + dev_err(di->dev, "failed to register FG psy\n"); + goto free_inst_curr_wq; + } + + di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); + ab8500_fg_coulomb_counter(di, true); + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_fg_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_fg_irq[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_fg_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_fg_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + ret = ab8500_fg_sysfs_init(di); + if (ret) { + dev_err(di->dev, "failed to create sysfs entry\n"); + goto free_irq; + } + + /* Calibrate the fg first time */ + di->flags.calibrate = true; + di->calib_state = AB8500_FG_CALIB_INIT; + + /* Run the FG algorithm */ + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + + list_add_tail(&di->node, &ab8500_fg_list); + + return ret; + +free_irq: + power_supply_unregister(&di->fg_psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name); + free_irq(irq, di); + } +free_inst_curr_wq: + destroy_workqueue(di->inst_curr_wq); +free_fg_wq: + destroy_workqueue(di->fg_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_fg_driver = { + .probe = ab8500_fg_probe, + .remove = __devexit_p(ab8500_fg_remove), + .suspend = ab8500_fg_suspend, + .resume = ab8500_fg_resume, + .driver = { + .name = "ab8500-fg", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_fg_init(void) +{ + return platform_driver_register(&ab8500_fg_driver); +} + +static void __exit ab8500_fg_exit(void) +{ + platform_driver_unregister(&ab8500_fg_driver); +} + +subsys_initcall_sync(ab8500_fg_init); +module_exit(ab8500_fg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab8500-fg"); +MODULE_DESCRIPTION("AB8500 Fuel Gauge driver"); diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c new file mode 100644 index 00000000000..bb0fa42b109 --- /dev/null +++ b/drivers/power/abx500_chargalg.c @@ -0,0 +1,1920 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Charging algorithm driver for abx500 variants + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/kobject.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ux500_chargalg.h> +#include <linux/mfd/abx500/ab5500-bm.h> + +/* Watchdog kick interval */ +#define CHG_WD_INTERVAL (6 * HZ) + +/* End-of-charge criteria counter */ +#define EOC_COND_CNT 10 + +/* Recharge criteria counter */ +#define RCH_COND_CNT 3 + +#define to_abx500_chargalg_device_info(x) container_of((x), \ + struct abx500_chargalg, chargalg_psy); + +enum abx500_chargers { + NO_CHG, + AC_CHG, + USB_CHG, +}; + +struct abx500_chargalg_charger_info { + enum abx500_chargers conn_chg; + enum abx500_chargers prev_conn_chg; + enum abx500_chargers online_chg; + enum abx500_chargers prev_online_chg; + enum abx500_chargers charger_type; + bool usb_chg_ok; + bool ac_chg_ok; + int usb_volt; + int usb_curr; + int ac_volt; + int ac_curr; + int usb_vset; + int usb_iset; + int ac_vset; + int ac_iset; +}; + +struct abx500_chargalg_suspension_status { + bool suspended_change; + bool ac_suspended; + bool usb_suspended; +}; + +struct abx500_chargalg_battery_data { + int temp; + int volt; + int avg_curr; + int inst_curr; + int percent; +}; + +enum abx500_chargalg_states { + STATE_HANDHELD_INIT, + STATE_HANDHELD, + STATE_CHG_NOT_OK_INIT, + STATE_CHG_NOT_OK, + STATE_HW_TEMP_PROTECT_INIT, + STATE_HW_TEMP_PROTECT, + STATE_NORMAL_INIT, + STATE_NORMAL, + STATE_WAIT_FOR_RECHARGE_INIT, + STATE_WAIT_FOR_RECHARGE, + STATE_MAINTENANCE_A_INIT, + STATE_MAINTENANCE_A, + STATE_MAINTENANCE_B_INIT, + STATE_MAINTENANCE_B, + STATE_TEMP_UNDEROVER_INIT, + STATE_TEMP_UNDEROVER, + STATE_TEMP_LOWHIGH_INIT, + STATE_TEMP_LOWHIGH, + STATE_SUSPENDED_INIT, + STATE_SUSPENDED, + STATE_OVV_PROTECT_INIT, + STATE_OVV_PROTECT, + STATE_SAFETY_TIMER_EXPIRED_INIT, + STATE_SAFETY_TIMER_EXPIRED, + STATE_BATT_REMOVED_INIT, + STATE_BATT_REMOVED, + STATE_WD_EXPIRED_INIT, + STATE_WD_EXPIRED, +}; + +static const char *states[] = { + "HANDHELD_INIT", + "HANDHELD", + "CHG_NOT_OK_INIT", + "CHG_NOT_OK", + "HW_TEMP_PROTECT_INIT", + "HW_TEMP_PROTECT", + "NORMAL_INIT", + "NORMAL", + "WAIT_FOR_RECHARGE_INIT", + "WAIT_FOR_RECHARGE", + "MAINTENANCE_A_INIT", + "MAINTENANCE_A", + "MAINTENANCE_B_INIT", + "MAINTENANCE_B", + "TEMP_UNDEROVER_INIT", + "TEMP_UNDEROVER", + "TEMP_LOWHIGH_INIT", + "TEMP_LOWHIGH", + "SUSPENDED_INIT", + "SUSPENDED", + "OVV_PROTECT_INIT", + "OVV_PROTECT", + "SAFETY_TIMER_EXPIRED_INIT", + "SAFETY_TIMER_EXPIRED", + "BATT_REMOVED_INIT", + "BATT_REMOVED", + "WD_EXPIRED_INIT", + "WD_EXPIRED", +}; + +struct abx500_chargalg_events { + bool batt_unknown; + bool mainextchnotok; + bool batt_ovv; + bool batt_rem; + bool btemp_underover; + bool btemp_lowhigh; + bool main_thermal_prot; + bool usb_thermal_prot; + bool main_ovv; + bool vbus_ovv; + bool usbchargernotok; + bool safety_timer_expired; + bool maintenance_timer_expired; + bool ac_wd_expired; + bool usb_wd_expired; + bool ac_cv_active; + bool usb_cv_active; + bool vbus_collapsed; +}; + +/** + * struct abx500_charge_curr_maximization - Charger maximization parameters + * @original_iset: the non optimized/maximised charger current + * @current_iset: the charging current used at this moment + * @test_delta_i: the delta between the current we want to charge and the + current that is really going into the battery + * @condition_cnt: number of iterations needed before a new charger current + is set + * @max_current: maximum charger current + * @wait_cnt: to avoid too fast current step down in case of charger + * voltage collapse, we insert this delay between step + * down + * @level: tells in how many steps the charging current has been + increased + */ +struct abx500_charge_curr_maximization { + int original_iset; + int current_iset; + int test_delta_i; + int condition_cnt; + int max_current; + int wait_cnt; + u8 level; +}; + +enum maxim_ret { + MAXIM_RET_NOACTION, + MAXIM_RET_CHANGE, + MAXIM_RET_IBAT_TOO_HIGH, +}; + +/** + * struct abx500_chargalg - abx500 Charging algorithm device information + * @dev: pointer to the structure device + * @charge_status: battery operating status + * @eoc_cnt: counter used to determine end-of_charge + * @rch_cnt: counter used to determine start of recharge + * @maintenance_chg: indicate if maintenance charge is active + * @t_hyst_norm temperature hysteresis when the temperature has been + * over or under normal limits + * @t_hyst_lowhigh temperature hysteresis when the temperature has been + * over or under the high or low limits + * @charge_state: current state of the charging algorithm + * @ccm charging current maximization parameters + * @chg_info: information about connected charger types + * @batt_data: data of the battery + * @susp_status: current charger suspension status + * @pdata: pointer to the abx500_chargalg platform data + * @bat: pointer to the abx500_bm platform data + * @chargalg_psy: structure that holds the battery properties exposed by + * the charging algorithm + * @events: structure for information about events triggered + * @chargalg_wq: work queue for running the charging algorithm + * @chargalg_periodic_work: work to run the charging algorithm periodically + * @chargalg_wd_work: work to kick the charger watchdog periodically + * @chargalg_work: work to run the charging algorithm instantly + * @safety_timer: charging safety timer + * @maintenance_timer: maintenance charging timer + * @chargalg_kobject: structure of type kobject + */ +struct abx500_chargalg { + struct device *dev; + int charge_status; + int eoc_cnt; + int rch_cnt; + bool maintenance_chg; + int t_hyst_norm; + int t_hyst_lowhigh; + enum abx500_chargalg_states charge_state; + struct abx500_charge_curr_maximization ccm; + struct abx500_chargalg_charger_info chg_info; + struct abx500_chargalg_battery_data batt_data; + struct abx500_chargalg_suspension_status susp_status; + struct abx500_chargalg_platform_data *pdata; + struct abx500_bm_data *bat; + struct power_supply chargalg_psy; + struct ux500_charger *ac_chg; + struct ux500_charger *usb_chg; + struct abx500_chargalg_events events; + struct workqueue_struct *chargalg_wq; + struct delayed_work chargalg_periodic_work; + struct delayed_work chargalg_wd_work; + struct work_struct chargalg_work; + struct timer_list safety_timer; + struct timer_list maintenance_timer; + struct kobject chargalg_kobject; +}; + +/* Main battery properties */ +static enum power_supply_property abx500_chargalg_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, +}; + +/** + * abx500_chargalg_safety_timer_expired() - Expiration of the safety timer + * @data: pointer to the abx500_chargalg structure + * + * This function gets called when the safety timer for the charger + * expires + */ +static void abx500_chargalg_safety_timer_expired(unsigned long data) +{ + struct abx500_chargalg *di = (struct abx500_chargalg *) data; + dev_err(di->dev, "Safety timer expired\n"); + di->events.safety_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_maintenance_timer_expired() - Expiration of + * the maintenance timer + * @i: pointer to the abx500_chargalg structure + * + * This function gets called when the maintenence timer + * expires + */ +static void abx500_chargalg_maintenance_timer_expired(unsigned long data) +{ + + struct abx500_chargalg *di = (struct abx500_chargalg *) data; + dev_dbg(di->dev, "Maintenance timer expired\n"); + di->events.maintenance_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_state_to() - Change charge state + * @di: pointer to the abx500_chargalg structure + * + * This function gets called when a charge state change should occur + */ +static void abx500_chargalg_state_to(struct abx500_chargalg *di, + enum abx500_chargalg_states state) +{ + dev_dbg(di->dev, + "State changed: %s (From state: [%d] %s =to=> [%d] %s )\n", + di->charge_state == state ? "NO" : "YES", + di->charge_state, + states[di->charge_state], + state, + states[state]); + + di->charge_state = state; +} + +/** + * abx500_chargalg_check_charger_connection() - Check charger connection change + * @di: pointer to the abx500_chargalg structure + * + * This function will check if there is a change in the charger connection + * and change charge state accordingly. AC has precedence over USB. + */ +static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di) +{ + if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg || + di->susp_status.suspended_change) { + /* + * Charger state changed or suspension + * has changed since last update + */ + if ((di->chg_info.conn_chg & AC_CHG) && + !di->susp_status.ac_suspended) { + dev_dbg(di->dev, "Charging source is AC\n"); + if (di->chg_info.charger_type != AC_CHG) { + di->chg_info.charger_type = AC_CHG; + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + } else if ((di->chg_info.conn_chg & USB_CHG) && + !di->susp_status.usb_suspended) { + dev_dbg(di->dev, "Charging source is USB\n"); + di->chg_info.charger_type = USB_CHG; + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } else if (di->chg_info.conn_chg && + (di->susp_status.ac_suspended || + di->susp_status.usb_suspended)) { + dev_dbg(di->dev, "Charging is suspended\n"); + di->chg_info.charger_type = NO_CHG; + abx500_chargalg_state_to(di, STATE_SUSPENDED_INIT); + } else { + dev_dbg(di->dev, "Charging source is OFF\n"); + di->chg_info.charger_type = NO_CHG; + abx500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + di->chg_info.prev_conn_chg = di->chg_info.conn_chg; + di->susp_status.suspended_change = false; + } + return di->chg_info.conn_chg; +} + +/** + * abx500_chargalg_start_safety_timer() - Start charging safety timer + * @di: pointer to the abx500_chargalg structure + * + * The safety timer is used to avoid overcharging of old or bad batteries. + * There are different timers for AC and USB + */ +static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di) +{ + unsigned long timer_expiration = 0; + + switch (di->chg_info.charger_type) { + case AC_CHG: + timer_expiration = + round_jiffies(jiffies + + (di->bat->main_safety_tmr_h * 3600 * HZ)); + break; + + case USB_CHG: + timer_expiration = + round_jiffies(jiffies + + (di->bat->usb_safety_tmr_h * 3600 * HZ)); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } + + di->events.safety_timer_expired = false; + di->safety_timer.expires = timer_expiration; + if (!timer_pending(&di->safety_timer)) + add_timer(&di->safety_timer); + else + mod_timer(&di->safety_timer, timer_expiration); +} + +/** + * abx500_chargalg_stop_safety_timer() - Stop charging safety timer + * @di: pointer to the abx500_chargalg structure + * + * The safety timer is stopped whenever the NORMAL state is exited + */ +static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di) +{ + di->events.safety_timer_expired = false; + del_timer(&di->safety_timer); +} + +/** + * abx500_chargalg_start_maintenance_timer() - Start charging maintenance timer + * @di: pointer to the abx500_chargalg structure + * @duration: duration of ther maintenance timer in hours + * + * The maintenance timer is used to maintain the charge in the battery once + * the battery is considered full. These timers are chosen to match the + * discharge curve of the battery + */ +static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di, + int duration) +{ + unsigned long timer_expiration; + + /* Convert from hours to jiffies */ + timer_expiration = round_jiffies(jiffies + (duration * 3600 * HZ)); + + di->events.maintenance_timer_expired = false; + di->maintenance_timer.expires = timer_expiration; + if (!timer_pending(&di->maintenance_timer)) + add_timer(&di->maintenance_timer); + else + mod_timer(&di->maintenance_timer, timer_expiration); +} + +/** + * abx500_chargalg_stop_maintenance_timer() - Stop maintenance timer + * @di: pointer to the abx500_chargalg structure + * + * The maintenance timer is stopped whenever maintenance ends or when another + * state is entered + */ +static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di) +{ + di->events.maintenance_timer_expired = false; + del_timer(&di->maintenance_timer); +} + +/** + * abx500_chargalg_kick_watchdog() - Kick charger watchdog + * @di: pointer to the abx500_chargalg structure + * + * The charger watchdog have to be kicked periodically whenever the charger is + * on, else the ABB will reset the system + */ +static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di) +{ + /* Check if charger exists and kick watchdog if charging */ + if (di->ac_chg && di->ac_chg->ops.kick_wd && + di->chg_info.online_chg & AC_CHG) + return di->ac_chg->ops.kick_wd(di->ac_chg); + else if (di->usb_chg && di->usb_chg->ops.kick_wd && + di->chg_info.online_chg & USB_CHG) + return di->usb_chg->ops.kick_wd(di->usb_chg); + + return -ENXIO; +} + +/** + * abx500_chargalg_ac_en() - Turn on/off the AC charger + * @di: pointer to the abx500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The AC charger will be turned on/off with the requested charge voltage and + * current + */ +static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->ac_chg || !di->ac_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->ac_chg->max_out_volt) + vset = min(vset, di->ac_chg->max_out_volt); + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + di->chg_info.ac_iset = iset; + di->chg_info.ac_vset = vset; + + return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset); +} + +/** + * abx500_chargalg_usb_en() - Turn on/off the USB charger + * @di: pointer to the abx500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The USB charger will be turned on/off with the requested charge voltage and + * current + */ +static int abx500_chargalg_usb_en(struct abx500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->usb_chg || !di->usb_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->usb_chg->max_out_volt) + vset = min(vset, di->usb_chg->max_out_volt); + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + di->chg_info.usb_iset = iset; + di->chg_info.usb_vset = vset; + + return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset); +} + +/** + * abx500_chargalg_update_chg_curr() - Update charger current + * @di: pointer to the abx500_chargalg structure + * @iset: requested charger output current + * + * The charger output current will be updated for the charger + * that is currently in use + */ +static int abx500_chargalg_update_chg_curr(struct abx500_chargalg *di, + int iset) +{ + /* Check if charger exists and update current if charging */ + if (di->ac_chg && di->ac_chg->ops.update_curr && + di->chg_info.charger_type & AC_CHG) { + /* + * Select maximum of what both the charger + * and the battery supports + */ + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + di->chg_info.ac_iset = iset; + + return di->ac_chg->ops.update_curr(di->ac_chg, iset); + } else if (di->usb_chg && di->usb_chg->ops.update_curr && + di->chg_info.charger_type & USB_CHG) { + /* + * Select maximum of what both the charger + * and the battery supports + */ + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + di->chg_info.usb_iset = iset; + + return di->usb_chg->ops.update_curr(di->usb_chg, iset); + } + + return -ENXIO; +} + +/** + * abx500_chargalg_stop_charging() - Stop charging + * @di: pointer to the abx500_chargalg structure + * + * This function is called from any state where charging should be stopped. + * All charging is disabled and all status parameters and timers are changed + * accordingly + */ +static void abx500_chargalg_stop_charging(struct abx500_chargalg *di) +{ + abx500_chargalg_ac_en(di, false, 0, 0); + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(&di->chargalg_psy); +} + +/** + * abx500_chargalg_hold_charging() - Pauses charging + * @di: pointer to the abx500_chargalg structure + * + * This function is called in the case where maintenance charging has been + * disabled and instead a battery voltage mode is entered to check when the + * battery voltage has reached a certain recharge voltage + */ +static void abx500_chargalg_hold_charging(struct abx500_chargalg *di) +{ + abx500_chargalg_ac_en(di, false, 0, 0); + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(&di->chargalg_psy); +} + +/** + * abx500_chargalg_start_charging() - Start the charger + * @di: pointer to the abx500_chargalg structure + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * A charger will be enabled depending on the requested charger type that was + * detected previously. + */ +static void abx500_chargalg_start_charging(struct abx500_chargalg *di, + int vset, int iset) +{ + switch (di->chg_info.charger_type) { + case AC_CHG: + dev_dbg(di->dev, + "AC parameters: Vset %d, Ich %d\n", vset, iset); + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_ac_en(di, true, vset, iset); + break; + + case USB_CHG: + dev_dbg(di->dev, + "USB parameters: Vset %d, Ich %d\n", vset, iset); + abx500_chargalg_ac_en(di, false, 0, 0); + abx500_chargalg_usb_en(di, true, vset, iset); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } +} + +/** + * abx500_chargalg_check_temp() - Check battery temperature ranges + * @di: pointer to the abx500_chargalg structure + * + * The battery temperature is checked against the predefined limits and the + * charge state is changed accordingly + */ +static void abx500_chargalg_check_temp(struct abx500_chargalg *di) +{ + if (di->batt_data.temp > (di->bat->temp_low + di->t_hyst_norm) && + di->batt_data.temp < (di->bat->temp_high - di->t_hyst_norm)) { + /* Temp OK! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = 0; + } else { + if (((di->batt_data.temp >= di->bat->temp_high) && + (di->batt_data.temp < + (di->bat->temp_over - di->t_hyst_lowhigh))) || + ((di->batt_data.temp > + (di->bat->temp_under + di->t_hyst_lowhigh)) && + (di->batt_data.temp <= di->bat->temp_low))) { + /* TEMP minor!!!!! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = true; + di->t_hyst_norm = di->bat->temp_hysteresis; + di->t_hyst_lowhigh = 0; + } else if (di->batt_data.temp <= di->bat->temp_under || + di->batt_data.temp >= di->bat->temp_over) { + /* TEMP major!!!!! */ + di->events.btemp_underover = true; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = di->bat->temp_hysteresis; + } else { + /* Within hysteresis */ + dev_dbg(di->dev, "Within hysteresis limit temp: %d " + "hyst_lowhigh %d, hyst normal %d\n", + di->batt_data.temp, di->t_hyst_lowhigh, + di->t_hyst_norm); + } + } +} + +/** + * abx500_chargalg_check_charger_voltage() - Check charger voltage + * @di: pointer to the abx500_chargalg structure + * + * Charger voltage is checked against maximum limit + */ +static void abx500_chargalg_check_charger_voltage(struct abx500_chargalg *di) +{ + if (di->chg_info.usb_volt > di->bat->chg_params->usb_volt_max) + di->chg_info.usb_chg_ok = false; + else + di->chg_info.usb_chg_ok = true; + + if (di->chg_info.ac_volt > di->bat->chg_params->ac_volt_max) + di->chg_info.ac_chg_ok = false; + else + di->chg_info.ac_chg_ok = true; + +} + +/** + * abx500_chargalg_end_of_charge() - Check if end-of-charge criteria is fulfilled + * @di: pointer to the abx500_chargalg structure + * + * End-of-charge criteria is fulfilled when the battery voltage is above a + * certain limit and the battery current is below a certain limit for a + * predefined number of consecutive seconds. If true, the battery is full + */ +static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di) +{ + if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING && + di->charge_state == STATE_NORMAL && + !di->maintenance_chg && (di->batt_data.volt >= + di->bat->bat_type[di->bat->batt_id].termination_vol || + di->events.usb_cv_active || di->events.ac_cv_active) && + di->batt_data.avg_curr < + di->bat->bat_type[di->bat->batt_id].termination_curr && + di->batt_data.avg_curr > 0) { + if (++di->eoc_cnt >= EOC_COND_CNT) { + di->eoc_cnt = 0; + di->charge_status = POWER_SUPPLY_STATUS_FULL; + di->maintenance_chg = true; + dev_dbg(di->dev, "EOC reached!\n"); + power_supply_changed(&di->chargalg_psy); + } else { + dev_dbg(di->dev, + " EOC limit reached for the %d" + " time, out of %d before EOC\n", + di->eoc_cnt, + EOC_COND_CNT); + } + } else { + di->eoc_cnt = 0; + } +} + +static void init_maxim_chg_curr(struct abx500_chargalg *di) +{ + di->ccm.original_iset = + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl; + di->ccm.current_iset = + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl; + di->ccm.test_delta_i = di->bat->maxi->charger_curr_step; + di->ccm.max_current = di->bat->maxi->chg_curr; + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.level = 0; +} + +/** + * abx500_chargalg_chg_curr_maxim - increases the charger current to + * compensate for the system load + * @di pointer to the abx500_chargalg structure + * + * This maximization function is used to raise the charger current to get the + * battery current as close to the optimal value as possible. The battery + * current during charging is affected by the system load + */ +static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di) +{ + int delta_i; + + if (!di->bat->maxi->ena_maxi) + return MAXIM_RET_NOACTION; + + delta_i = di->ccm.original_iset - di->batt_data.inst_curr; + + if (di->events.vbus_collapsed) { + dev_dbg(di->dev, "Charger voltage has collapsed %d\n", + di->ccm.wait_cnt); + if (di->ccm.wait_cnt == 0) { + dev_dbg(di->dev, "lowering current\n"); + di->ccm.wait_cnt++; + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.max_current = + di->ccm.current_iset - di->ccm.test_delta_i; + di->ccm.current_iset = di->ccm.max_current; + di->ccm.level--; + return MAXIM_RET_CHANGE; + } else { + dev_dbg(di->dev, "waiting\n"); + /* Let's go in here twice before lowering curr again */ + di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 3; + return MAXIM_RET_NOACTION; + } + } + + di->ccm.wait_cnt = 0; + + if ((di->batt_data.inst_curr > di->ccm.original_iset)) { + dev_dbg(di->dev, " Maximization Ibat (%dmA) too high" + " (limit %dmA) (current iset: %dmA)!\n", + di->batt_data.inst_curr, di->ccm.original_iset, + di->ccm.current_iset); + + if (di->ccm.current_iset == di->ccm.original_iset) + return MAXIM_RET_NOACTION; + + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.current_iset = di->ccm.original_iset; + di->ccm.level = 0; + + return MAXIM_RET_IBAT_TOO_HIGH; + } + + if (delta_i > di->ccm.test_delta_i && + (di->ccm.current_iset + di->ccm.test_delta_i) < + di->ccm.max_current) { + if (di->ccm.condition_cnt-- == 0) { + /* Increse the iset with cco.test_delta_i */ + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.current_iset += di->ccm.test_delta_i; + di->ccm.level++; + dev_dbg(di->dev, " Maximization needed, increase" + " with %d mA to %dmA (Optimal ibat: %d)" + " Level %d\n", + di->ccm.test_delta_i, + di->ccm.current_iset, + di->ccm.original_iset, + di->ccm.level); + return MAXIM_RET_CHANGE; + } else { + return MAXIM_RET_NOACTION; + } + } else { + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + return MAXIM_RET_NOACTION; + } +} + +static void handle_maxim_chg_curr(struct abx500_chargalg *di) +{ + enum maxim_ret ret; + int result; + + ret = abx500_chargalg_chg_curr_maxim(di); + switch (ret) { + case MAXIM_RET_CHANGE: + result = abx500_chargalg_update_chg_curr(di, + di->ccm.current_iset); + if (result) + dev_err(di->dev, "failed to set chg curr\n"); + break; + case MAXIM_RET_IBAT_TOO_HIGH: + result = abx500_chargalg_update_chg_curr(di, + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + if (result) + dev_err(di->dev, "failed to set chg curr\n"); + break; + + case MAXIM_RET_NOACTION: + default: + /* Do nothing..*/ + break; + } +} + +static int abx500_chargalg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct abx500_chargalg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_abx500_chargalg_device_info(psy); + /* For all psy where the driver name appears in any supplied_to */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + /* Initialize chargers if not already done */ + if (!di->ac_chg && + ext->type == POWER_SUPPLY_TYPE_MAINS) + di->ac_chg = psy_to_ux500_charger(ext); + else if (!di->usb_chg && + ext->type == POWER_SUPPLY_TYPE_USB) + di->usb_chg = psy_to_ux500_charger(ext); + + if (ext->get_property(ext, prop, &ret)) + continue; + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + /* Battery present */ + if (ret.intval) + di->events.batt_rem = false; + /* Battery removed */ + else + di->events.batt_rem = true; + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~AC_CHG; + } + /* AC connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= AC_CHG; + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~USB_CHG; + } + /* USB connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= USB_CHG; + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_ONLINE: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC offline */ + if (!ret.intval && + (di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~AC_CHG; + } + /* AC online */ + else if (ret.intval && + !(di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= AC_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB offline */ + if (!ret.intval && + (di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~USB_CHG; + } + /* USB online */ + else if (ret.intval && + !(di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= USB_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_HEALTH: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.mainextchnotok = true; + di->events.main_thermal_prot = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.ac_wd_expired = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.main_thermal_prot = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.main_thermal_prot = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.main_ovv = true; + di->events.mainextchnotok = false; + di->events.main_thermal_prot = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.main_thermal_prot = false; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + default: + break; + } + break; + + case POWER_SUPPLY_TYPE_USB: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.usbchargernotok = true; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.usb_wd_expired = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.usb_thermal_prot = true; + di->events.usbchargernotok = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.vbus_ovv = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + default: + break; + } + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_volt = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.ac_cv_active = true; + else + di->events.ac_cv_active = false; + + break; + case POWER_SUPPLY_TYPE_USB: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.usb_cv_active = true; + else + di->events.usb_cv_active = false; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (ret.intval) + di->events.batt_unknown = false; + else + di->events.batt_unknown = true; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TEMP: + di->batt_data.temp = ret.intval / 10; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.inst_curr = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_CURRENT_AVG: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.avg_curr = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + if (ret.intval) + di->events.vbus_collapsed = true; + else + di->events.vbus_collapsed = false; + break; + default: + break; + } + break; + case POWER_SUPPLY_PROP_CAPACITY: + di->batt_data.percent = ret.intval; + break; + default: + break; + } + } + return 0; +} + +/** + * abx500_chargalg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void abx500_chargalg_external_power_changed(struct power_supply *psy) +{ + struct abx500_chargalg *di = to_abx500_chargalg_device_info(psy); + + /* + * Trigger execution of the algorithm instantly and read + * all power_supply properties there instead + */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_algorithm() - Main function for the algorithm + * @di: pointer to the abx500_chargalg structure + * + * This is the main control function for the charging algorithm. + * It is called periodically or when something happens that will + * trigger a state change + */ +static void abx500_chargalg_algorithm(struct abx500_chargalg *di) +{ + int charger_status; + + /* Collect data from all power_supply class devices */ + class_for_each_device(power_supply_class, NULL, + &di->chargalg_psy, abx500_chargalg_get_ext_psy_data); + + abx500_chargalg_end_of_charge(di); + abx500_chargalg_check_temp(di); + abx500_chargalg_check_charger_voltage(di); + + charger_status = abx500_chargalg_check_charger_connection(di); + /* + * First check if we have a charger connected. + * Also we don't allow charging of unknown batteries if configured + * this way + */ + if (!charger_status || + (di->events.batt_unknown && !di->bat->chg_unknown_bat)) { + if (di->charge_state != STATE_HANDHELD) { + di->events.safety_timer_expired = false; + abx500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + } + + /* If suspended, we should not continue checking the flags */ + else if (di->charge_state == STATE_SUSPENDED_INIT || + di->charge_state == STATE_SUSPENDED) { + /* We don't do anything here, just don,t continue */ + } + + /* Safety timer expiration */ + else if (di->events.safety_timer_expired) { + if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED) + abx500_chargalg_state_to(di, + STATE_SAFETY_TIMER_EXPIRED_INIT); + } + /* + * Check if any interrupts has occured + * that will prevent us from charging + */ + + /* Battery removed */ + else if (di->events.batt_rem) { + if (di->charge_state != STATE_BATT_REMOVED) + abx500_chargalg_state_to(di, STATE_BATT_REMOVED_INIT); + } + /* Main or USB charger not ok. */ + else if (di->events.mainextchnotok || di->events.usbchargernotok) { + /* + * If vbus_collapsed is set, we have to lower the charger + * current, which is done in the normal state below + */ + if (di->charge_state != STATE_CHG_NOT_OK && + !di->events.vbus_collapsed) + abx500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT); + } + /* VBUS, Main or VBAT OVV. */ + else if (di->events.vbus_ovv || + di->events.main_ovv || + di->events.batt_ovv || + !di->chg_info.usb_chg_ok || + !di->chg_info.ac_chg_ok) { + if (di->charge_state != STATE_OVV_PROTECT) + abx500_chargalg_state_to(di, STATE_OVV_PROTECT_INIT); + } + /* USB Thermal, stop charging */ + else if (di->events.main_thermal_prot || + di->events.usb_thermal_prot) { + if (di->charge_state != STATE_HW_TEMP_PROTECT) + abx500_chargalg_state_to(di, + STATE_HW_TEMP_PROTECT_INIT); + } + /* Battery temp over/under */ + else if (di->events.btemp_underover) { + if (di->charge_state != STATE_TEMP_UNDEROVER) + abx500_chargalg_state_to(di, + STATE_TEMP_UNDEROVER_INIT); + } + /* Watchdog expired */ + else if (di->events.ac_wd_expired || + di->events.usb_wd_expired) { + if (di->charge_state != STATE_WD_EXPIRED) + abx500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT); + } + /* Battery temp high/low */ + else if (di->events.btemp_lowhigh) { + if (di->charge_state != STATE_TEMP_LOWHIGH) + abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT); + } + + dev_dbg(di->dev, + "[CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d " + "State %s Active_chg %d Chg_status %d AC %d USB %d " + "AC_online %d USB_online %d AC_CV %d USB_CV %d AC_I %d " + "USB_I %d AC_Vset %d AC_Iset %d USB_Vset %d USB_Iset %d\n", + di->batt_data.volt, + di->batt_data.avg_curr, + di->batt_data.inst_curr, + di->batt_data.temp, + di->batt_data.percent, + di->maintenance_chg, + states[di->charge_state], + di->chg_info.charger_type, + di->charge_status, + di->chg_info.conn_chg & AC_CHG, + di->chg_info.conn_chg & USB_CHG, + di->chg_info.online_chg & AC_CHG, + di->chg_info.online_chg & USB_CHG, + di->events.ac_cv_active, + di->events.usb_cv_active, + di->chg_info.ac_curr, + di->chg_info.usb_curr, + di->chg_info.ac_vset, + di->chg_info.ac_iset, + di->chg_info.usb_vset, + di->chg_info.usb_iset); + + switch (di->charge_state) { + case STATE_HANDHELD_INIT: + abx500_chargalg_stop_charging(di); + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + abx500_chargalg_state_to(di, STATE_HANDHELD); + /* Intentional fallthrough */ + + case STATE_HANDHELD: + break; + + case STATE_SUSPENDED_INIT: + if (di->susp_status.ac_suspended) + abx500_chargalg_ac_en(di, false, 0, 0); + if (di->susp_status.usb_suspended) + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + abx500_chargalg_state_to(di, STATE_SUSPENDED); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_SUSPENDED: + /* CHARGING is suspended */ + break; + + case STATE_BATT_REMOVED_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_BATT_REMOVED); + /* Intentional fallthrough */ + + case STATE_BATT_REMOVED: + if (!di->events.batt_rem) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_HW_TEMP_PROTECT_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_HW_TEMP_PROTECT); + /* Intentional fallthrough */ + + case STATE_HW_TEMP_PROTECT: + if (!di->events.main_thermal_prot && + !di->events.usb_thermal_prot) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_OVV_PROTECT_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_OVV_PROTECT); + /* Intentional fallthrough */ + + case STATE_OVV_PROTECT: + if (!di->events.vbus_ovv && + !di->events.main_ovv && + !di->events.batt_ovv && + di->chg_info.usb_chg_ok && + di->chg_info.ac_chg_ok) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_CHG_NOT_OK_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_CHG_NOT_OK); + /* Intentional fallthrough */ + + case STATE_CHG_NOT_OK: + if (!di->events.mainextchnotok && + !di->events.usbchargernotok) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_SAFETY_TIMER_EXPIRED_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_SAFETY_TIMER_EXPIRED); + /* Intentional fallthrough */ + + case STATE_SAFETY_TIMER_EXPIRED: + /* We exit this state when charger is removed */ + break; + + case STATE_NORMAL_INIT: + abx500_chargalg_start_charging(di, + di->bat->bat_type[di->bat->batt_id].normal_vol_lvl, + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + abx500_chargalg_state_to(di, STATE_NORMAL); + abx500_chargalg_start_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + init_maxim_chg_curr(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->eoc_cnt = 0; + di->maintenance_chg = false; + power_supply_changed(&di->chargalg_psy); + + break; + + case STATE_NORMAL: + handle_maxim_chg_curr(di); + if (di->charge_status == POWER_SUPPLY_STATUS_FULL && + di->maintenance_chg) { + if (di->bat->no_maintenance) + abx500_chargalg_state_to(di, + STATE_WAIT_FOR_RECHARGE_INIT); + else + abx500_chargalg_state_to(di, + STATE_MAINTENANCE_A_INIT); + } + break; + + /* This state will be used when the maintenance state is disabled */ + case STATE_WAIT_FOR_RECHARGE_INIT: + abx500_chargalg_hold_charging(di); + abx500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE); + di->rch_cnt = RCH_COND_CNT; + /* Intentional fallthrough */ + + case STATE_WAIT_FOR_RECHARGE: + if (di->batt_data.volt <= + di->bat->bat_type[di->bat->batt_id].recharge_vol) { + if (di->rch_cnt-- == 0) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } else + di->rch_cnt = RCH_COND_CNT; + break; + + case STATE_MAINTENANCE_A_INIT: + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_start_maintenance_timer(di, + di->bat->bat_type[ + di->bat->batt_id].maint_a_chg_timer_h); + abx500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].maint_a_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].maint_a_cur_lvl); + abx500_chargalg_state_to(di, STATE_MAINTENANCE_A); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_A: + if (di->events.maintenance_timer_expired) { + abx500_chargalg_stop_maintenance_timer(di); + abx500_chargalg_state_to(di, STATE_MAINTENANCE_B_INIT); + } + break; + + case STATE_MAINTENANCE_B_INIT: + abx500_chargalg_start_maintenance_timer(di, + di->bat->bat_type[ + di->bat->batt_id].maint_b_chg_timer_h); + abx500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].maint_b_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].maint_b_cur_lvl); + abx500_chargalg_state_to(di, STATE_MAINTENANCE_B); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_B: + if (di->events.maintenance_timer_expired) { + abx500_chargalg_stop_maintenance_timer(di); + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + break; + + case STATE_TEMP_LOWHIGH_INIT: + abx500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].low_high_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].low_high_cur_lvl); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_TEMP_LOWHIGH: + if (!di->events.btemp_lowhigh) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_WD_EXPIRED_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_WD_EXPIRED); + /* Intentional fallthrough */ + + case STATE_WD_EXPIRED: + if (!di->events.ac_wd_expired && + !di->events.usb_wd_expired) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_TEMP_UNDEROVER_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_TEMP_UNDEROVER); + /* Intentional fallthrough */ + + case STATE_TEMP_UNDEROVER: + if (!di->events.btemp_underover) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + } + + /* Start charging directly if the new state is a charge state */ + if (di->charge_state == STATE_NORMAL_INIT || + di->charge_state == STATE_MAINTENANCE_A_INIT || + di->charge_state == STATE_MAINTENANCE_B_INIT) + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_periodic_work() - Periodic work for the algorithm + * @work: pointer to the work_struct structure + * + * Work queue function for the charging algorithm + */ +static void abx500_chargalg_periodic_work(struct work_struct *work) +{ + struct abx500_chargalg *di = container_of(work, + struct abx500_chargalg, chargalg_periodic_work.work); + + abx500_chargalg_algorithm(di); + + /* + * If a charger is connected then the battery has to be monitored + * frequently, else the work can be delayed. + */ + if (di->chg_info.conn_chg) + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bat->interval_charging * HZ); + else + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bat->interval_not_charging * HZ); +} + +/** + * abx500_chargalg_wd_work() - periodic work to kick the charger watchdog + * @work: pointer to the work_struct structure + * + * Work queue function for kicking the charger watchdog + */ +static void abx500_chargalg_wd_work(struct work_struct *work) +{ + int ret; + struct abx500_chargalg *di = container_of(work, + struct abx500_chargalg, chargalg_wd_work.work); + + dev_dbg(di->dev, "abx500_chargalg_wd_work\n"); + + ret = abx500_chargalg_kick_watchdog(di); + if (ret < 0) + dev_err(di->dev, "failed to kick watchdog\n"); + + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, CHG_WD_INTERVAL); +} + +/** + * abx500_chargalg_work() - Work to run the charging algorithm instantly + * @work: pointer to the work_struct structure + * + * Work queue function for calling the charging algorithm + */ +static void abx500_chargalg_work(struct work_struct *work) +{ + struct abx500_chargalg *di = container_of(work, + struct abx500_chargalg, chargalg_work); + + abx500_chargalg_algorithm(di); +} + +/** + * abx500_chargalg_get_property() - get the chargalg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * chargalg properties by reading the sysfs files. + * status: charging/discharging/full/unknown + * health: health of the battery + * Returns error code in case of failure else 0 on success + */ +static int abx500_chargalg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct abx500_chargalg *di; + + di = to_abx500_chargalg_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (di->events.batt_ovv) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (di->events.btemp_underover) { + if (di->batt_data.temp <= di->bat->temp_under) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + } else { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } + break; + default: + return -EINVAL; + } + return 0; +} + +/* Exposure to the sysfs interface */ + +/** + * abx500_chargalg_sysfs_charger() - sysfs store operations + * @kobj: pointer to the struct kobject + * @attr: pointer to the struct attribute + * @buf: buffer that holds the parameter passed from userspace + * @length: length of the parameter passed + * + * Returns length of the buffer(input taken from user space) on success + * else error code on failure + * The operation to be performed on passing the parameters from the user space. + */ +static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t length) +{ + struct abx500_chargalg *di = container_of(kobj, + struct abx500_chargalg, chargalg_kobject); + long int param; + int ac_usb; + int ret; + char entry = *attr->name; + + switch (entry) { + case 'c': + ret = strict_strtol(buf, 10, ¶m); + if (ret < 0) + return ret; + + ac_usb = param; + switch (ac_usb) { + case 0: + /* Disable charging */ + di->susp_status.ac_suspended = true; + di->susp_status.usb_suspended = true; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 1: + /* Enable AC Charging */ + di->susp_status.ac_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 2: + /* Enable USB charging */ + di->susp_status.usb_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + default: + dev_info(di->dev, "Wrong input\n" + "Enter 0. Disable AC/USB Charging\n" + "1. Enable AC charging\n" + "2. Enable USB Charging\n"); + }; + break; + }; + return strlen(buf); +} + +static struct attribute abx500_chargalg_en_charger = \ +{ + .name = "chargalg", + .mode = S_IWUGO, +}; + +static struct attribute *abx500_chargalg_chg[] = { + &abx500_chargalg_en_charger, + NULL +}; + +const struct sysfs_ops abx500_chargalg_sysfs_ops = { + .store = abx500_chargalg_sysfs_charger, +}; + +static struct kobj_type abx500_chargalg_ktype = { + .sysfs_ops = &abx500_chargalg_sysfs_ops, + .default_attrs = abx500_chargalg_chg, +}; + +/** + * abx500_chargalg_sysfs_exit() - de-init of sysfs entry + * @di: pointer to the struct abx500_chargalg + * + * This function removes the entry in sysfs. + */ +static void abx500_chargalg_sysfs_exit(struct abx500_chargalg *di) +{ + kobject_del(&di->chargalg_kobject); +} + +/** + * abx500_chargalg_sysfs_init() - init of sysfs entry + * @di: pointer to the struct abx500_chargalg + * + * This function adds an entry in sysfs. + * Returns error code in case of failure else 0(on success) + */ +static int abx500_chargalg_sysfs_init(struct abx500_chargalg *di) +{ + int ret = 0; + + ret = kobject_init_and_add(&di->chargalg_kobject, + &abx500_chargalg_ktype, + NULL, "abx500_chargalg"); + if (ret < 0) + dev_err(di->dev, "failed to create sysfs entry\n"); + + return ret; +} +/* Exposure to the sysfs interface <<END>> */ + +#if defined(CONFIG_PM) +static int abx500_chargalg_resume(struct platform_device *pdev) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + /* Kick charger watchdog if charging (any charger online) */ + if (di->chg_info.online_chg) + queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0); + + /* + * Run the charging algorithm directly to be sure we don't + * do it too seldom + */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + return 0; +} + +static int abx500_chargalg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + if (di->chg_info.online_chg) + cancel_delayed_work_sync(&di->chargalg_wd_work); + + cancel_delayed_work_sync(&di->chargalg_periodic_work); + + return 0; +} +#else +#define abx500_chargalg_suspend NULL +#define abx500_chargalg_resume NULL +#endif + +static int __devexit abx500_chargalg_remove(struct platform_device *pdev) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + /* sysfs interface to enable/disbale charging from user space */ + abx500_chargalg_sysfs_exit(di); + + /* Delete the work queue */ + destroy_workqueue(di->chargalg_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->chargalg_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit abx500_chargalg_probe(struct platform_device *pdev) +{ + struct abx500_bm_plat_data *plat_data; + int ret = 0; + + struct abx500_chargalg *di = + kzalloc(sizeof(struct abx500_chargalg), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get device struct */ + di->dev = &pdev->dev; + + plat_data = pdev->dev.platform_data; + di->pdata = plat_data->chargalg; + di->bat = plat_data->battery; + + /* chargalg supply */ + di->chargalg_psy.name = "abx500_chargalg"; + di->chargalg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->chargalg_psy.properties = abx500_chargalg_props; + di->chargalg_psy.num_properties = ARRAY_SIZE(abx500_chargalg_props); + di->chargalg_psy.get_property = abx500_chargalg_get_property; + di->chargalg_psy.supplied_to = di->pdata->supplied_to; + di->chargalg_psy.num_supplicants = di->pdata->num_supplicants; + di->chargalg_psy.external_power_changed = + abx500_chargalg_external_power_changed; + + /* Initilialize safety timer */ + init_timer(&di->safety_timer); + di->safety_timer.function = abx500_chargalg_safety_timer_expired; + di->safety_timer.data = (unsigned long) di; + + /* Initilialize maintenance timer */ + init_timer(&di->maintenance_timer); + di->maintenance_timer.function = + abx500_chargalg_maintenance_timer_expired; + di->maintenance_timer.data = (unsigned long) di; + + /* Create a work queue for the chargalg */ + di->chargalg_wq = + create_singlethread_workqueue("abx500_chargalg_wq"); + if (di->chargalg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for chargalg */ + INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_periodic_work, + abx500_chargalg_periodic_work); + INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_wd_work, + abx500_chargalg_wd_work); + + /* Init work for chargalg */ + INIT_WORK(&di->chargalg_work, abx500_chargalg_work); + + /* To detect charger at startup */ + di->chg_info.prev_conn_chg = -1; + + /* Register chargalg power supply class */ + ret = power_supply_register(di->dev, &di->chargalg_psy); + if (ret) { + dev_err(di->dev, "failed to register chargalg psy\n"); + goto free_chargalg_wq; + } + + platform_set_drvdata(pdev, di); + + /* sysfs interface to enable/disable charging from user space */ + ret = abx500_chargalg_sysfs_init(di); + if (ret) { + dev_err(di->dev, "failed to create sysfs entry\n"); + goto free_psy; + } + + /* Run the charging algorithm */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + dev_info(di->dev, "probe success\n"); + return ret; + +free_psy: + power_supply_unregister(&di->chargalg_psy); +free_chargalg_wq: + destroy_workqueue(di->chargalg_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver abx500_chargalg_driver = { + .probe = abx500_chargalg_probe, + .remove = __devexit_p(abx500_chargalg_remove), + .suspend = abx500_chargalg_suspend, + .resume = abx500_chargalg_resume, + .driver = { + .name = "abx500-chargalg", + .owner = THIS_MODULE, + }, +}; + +static int __init abx500_chargalg_init(void) +{ + return platform_driver_register(&abx500_chargalg_driver); +} + +static void __exit abx500_chargalg_exit(void) +{ + platform_driver_unregister(&abx500_chargalg_driver); +} + +module_init(abx500_chargalg_init); +module_exit(abx500_chargalg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:abx500-chargalg"); +MODULE_DESCRIPTION("abx500 battery charging algorithm"); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index c7fd2c0e3f2..5547ba7e0ac 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -274,6 +274,13 @@ config REGULATOR_AD5398 This driver supports AD5398 and AD5821 current regulator chips. If building into module, its name is ad5398.ko. +config REGULATOR_AB5500 + bool "ST-Ericsson AB5500 Power Regulators" + depends on AB5500_CORE + help + This driver supports the regulators found on the ST-Ericsson mixed + signal AB5500 PMIC + config REGULATOR_AB8500 bool "ST-Ericsson AB8500 Power Regulators" depends on AB8500_CORE @@ -281,13 +288,41 @@ config REGULATOR_AB8500 This driver supports the regulators found on the ST-Ericsson mixed signal AB8500 PMIC +config REGULATOR_AB8500_EXT + bool "ST-Ericsson AB8500 External Regulators" + depends on REGULATOR_AB8500 + default y if REGULATOR_AB8500 + help + This driver supports the external regulator controls found on the + ST-Ericsson mixed signal AB8500 PMIC + +config REGULATOR_DBX500_PRCMU + bool + +config REGULATOR_DB5500_PRCMU + bool "ST-Ericsson DB5500 Voltage Domain Regulators" + depends on MFD_DB5500_PRCMU + select REGULATOR_DBX500_PRCMU + help + This driver supports the voltage domain regulators controlled by the + DB5500 PRCMU + config REGULATOR_DB8500_PRCMU bool "ST-Ericsson DB8500 Voltage Domain Regulators" depends on MFD_DB8500_PRCMU + select REGULATOR_DBX500_PRCMU help This driver supports the voltage domain regulators controlled by the DB8500 PRCMU +config REGULATOR_AB8500_DEBUG + bool "AB8500 regulator debug" + depends on REGULATOR_AB8500 + help + Say Y here to add debug functionality for ST-Ericsson + ab8500 regulators. This is a module that exposes a + number of settings and debug output in debugfs. + config REGULATOR_TPS6586X tristate "TI TPS6586X Power regulators" depends on MFD_TPS6586X diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 040d5aa6353..db44e020ea8 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -41,9 +41,14 @@ obj-$(CONFIG_REGULATOR_TPS6524X) += tps6524x-regulator.o obj-$(CONFIG_REGULATOR_TPS65912) += tps65912-regulator.o obj-$(CONFIG_REGULATOR_88PM8607) += 88pm8607.o obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o +obj-$(CONFIG_REGULATOR_AB5500) += ab5500.o obj-$(CONFIG_REGULATOR_AB8500) += ab8500.o +obj-$(CONFIG_REGULATOR_AB8500_EXT) += ab8500-ext.o +obj-$(CONFIG_REGULATOR_DBX500_PRCMU) += dbx500-prcmu.o +obj-$(CONFIG_REGULATOR_DB5500_PRCMU) += db5500-prcmu.o obj-$(CONFIG_REGULATOR_DB8500_PRCMU) += db8500-prcmu.o obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o obj-$(CONFIG_REGULATOR_AAT2870) += aat2870-regulator.o +obj-$(CONFIG_REGULATOR_AB8500_DEBUG) += ab8500-debug.o ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG diff --git a/drivers/regulator/ab5500.c b/drivers/regulator/ab5500.c new file mode 100644 index 00000000000..8c2c5ec9974 --- /dev/null +++ b/drivers/regulator/ab5500.c @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2011 ST-Ericsson SA + * + * License terms: GNU General Public License (GPL) version 2 + * + * Based on ab3100.c. + * + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/regulator/ab5500.h> + +#define AB5500_LDO_VDIGMIC_ST 0x50 + +#define AB5500_LDO_G_ST 0x78 +#define AB5500_LDO_G_PWR1 0x79 +#define AB5500_LDO_G_PWR0 0x7a + +#define AB5500_LDO_H_ST 0x7b +#define AB5500_LDO_H_PWR1 0x7c +#define AB5500_LDO_H_PWR0 0x7d + +#define AB5500_LDO_K_ST 0x7e +#define AB5500_LDO_K_PWR1 0x7f +#define AB5500_LDO_K_PWR0 0x80 + +#define AB5500_LDO_L_ST 0x81 +#define AB5500_LDO_L_PWR1 0x82 +#define AB5500_LDO_L_PWR0 0x83 + +/* In SIM bank */ +#define AB5500_SIM_SUP 0x14 + +#define AB5500_MBIAS2 0x01 + +#define AB5500_LDO_MODE_MASK (0x3 << 4) +#define AB5500_LDO_MODE_FULLPOWER (0x3 << 4) +#define AB5500_LDO_MODE_PWRCTRL (0x2 << 4) +#define AB5500_LDO_MODE_LOWPOWER (0x1 << 4) +#define AB5500_LDO_MODE_OFF (0x0 << 4) +#define AB5500_LDO_VOLT_MASK 0x07 + +#define AB5500_MBIAS2_ENABLE (0x1 << 1) +#define AB5500_MBIAS2_VOLT_MASK (0x1 << 2) +#define AB5500_MBIAS2_MODE_MASK (0x1 << 1) + +struct ab5500_regulator { + struct regulator_desc desc; + const int *voltages; + int num_holes; + bool pwrctrl; + bool enabled; + int enable_time; + u8 bank; + u8 reg; + u8 mode; + u8 update_mask; + u8 update_val_idle; + u8 update_val_normal; + u8 voltage_mask; +}; + +struct ab5500_regulators { + struct device *dev; + struct ab5500_regulator *regulator[AB5500_NUM_REGULATORS]; + struct regulator_dev *rdev[AB5500_NUM_REGULATORS]; +}; + +static int ab5500_regulator_enable_time(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + + return r->enable_time; /* microseconds */ +} + +static int ab5500_regulator_enable(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + int ret; + + ret = abx500_mask_and_set(ab5500->dev, r->bank, r->reg, + r->update_mask, r->mode); + if (ret < 0) + return ret; + + r->enabled = true; + + return 0; +} + +static int ab5500_regulator_disable(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + u8 regval = r->pwrctrl ? AB5500_LDO_MODE_PWRCTRL : AB5500_LDO_MODE_OFF; + int ret; + + ret = abx500_mask_and_set(ab5500->dev, r->bank, r->reg, + r->update_mask, regval); + if (ret < 0) + return ret; + + r->enabled = false; + + return 0; +} + +static unsigned int ab5500_regulator_get_mode(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + + if (r->mode == r->update_val_idle) + return REGULATOR_MODE_IDLE; + + return REGULATOR_MODE_NORMAL; +} + +static int ab5500_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + r->mode = r->update_val_normal; + break; + case REGULATOR_MODE_IDLE: + r->mode = r->update_val_idle; + break; + default: + return -EINVAL; + } + + if (r->enabled) + return ab5500_regulator_enable(rdev); + + return 0; +} + +static int ab5500_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + u8 regval; + int err; + + err = abx500_get_register_interruptible(ab5500->dev, + r->bank, r->reg, ®val); + if (err) { + dev_err(rdev_get_dev(rdev), "unable to get register 0x%x\n", + r->reg); + return err; + } + + switch (regval & r->update_mask) { + case AB5500_LDO_MODE_PWRCTRL: + case AB5500_LDO_MODE_OFF: + r->enabled = false; + break; + default: + r->enabled = true; + break; + } + + return r->enabled; +} + +static int +ab5500_regulator_list_voltage(struct regulator_dev *rdev, unsigned selector) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + unsigned n_voltages = r->desc.n_voltages; + int selindex; + int i; + + for (i = 0, selindex = 0; selindex < n_voltages; i++) { + int voltage = r->voltages[i]; + + if (!voltage) + continue; + + if (selindex == selector) + return voltage; + + selindex++; + } + + return -EINVAL; +} + +static int ab5500_regulator_fixed_get_voltage(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + + return r->voltages[0]; +} + +static int ab5500_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + u8 regval; + int ret; + + ret = abx500_get_register_interruptible(ab5500->dev, + r->bank, r->reg, ®val); + if (ret) { + dev_warn(rdev_get_dev(rdev), + "failed to get regulator value in register " + "%02x\n", r->reg); + return ret; + } + + regval &= r->voltage_mask; + if (regval >= r->desc.n_voltages + r->num_holes) + return -EINVAL; + + if (!r->voltages[regval]) + return -EINVAL; + + return r->voltages[regval]; +} + +static int ab5500_get_best_voltage_index(struct ab5500_regulator *r, + int min_uV, int max_uV) +{ + unsigned n_voltages = r->desc.n_voltages; + int bestmatch = INT_MAX; + int bestindex = -EINVAL; + int selindex; + int i; + + /* + * Locate the minimum voltage fitting the criteria on + * this regulator. The switchable voltages are not + * in strict falling order so we need to check them + * all for the best match. + */ + for (i = 0, selindex = 0; selindex < n_voltages; i++) { + int voltage = r->voltages[i]; + + if (!voltage) + continue; + + if (voltage <= max_uV && + voltage >= min_uV && + voltage < bestmatch) { + bestmatch = voltage; + bestindex = i; + } + + selindex++; + } + + return bestindex; +} + +static int ab5500_regulator_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, + unsigned *selector) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + int bestindex; + + bestindex = ab5500_get_best_voltage_index(r, min_uV, max_uV); + if (bestindex < 0) { + dev_warn(rdev_get_dev(rdev), + "requested %d<=x<=%d uV, out of range!\n", + min_uV, max_uV); + return bestindex; + } + + *selector = bestindex; + + return abx500_mask_and_set_register_interruptible(ab5500->dev, + r->bank, r->reg, r->voltage_mask, bestindex); + +} + +static struct regulator_ops ab5500_regulator_variable_ops = { + .enable = ab5500_regulator_enable, + .disable = ab5500_regulator_disable, + .is_enabled = ab5500_regulator_is_enabled, + .enable_time = ab5500_regulator_enable_time, + .get_voltage = ab5500_regulator_get_voltage, + .set_voltage = ab5500_regulator_set_voltage, + .list_voltage = ab5500_regulator_list_voltage, + .set_mode = ab5500_regulator_set_mode, + .get_mode = ab5500_regulator_get_mode, +}; + +static struct regulator_ops ab5500_regulator_fixed_ops = { + .enable = ab5500_regulator_enable, + .disable = ab5500_regulator_disable, + .is_enabled = ab5500_regulator_is_enabled, + .enable_time = ab5500_regulator_enable_time, + .get_voltage = ab5500_regulator_fixed_get_voltage, + .list_voltage = ab5500_regulator_list_voltage, + .set_mode = ab5500_regulator_set_mode, + .get_mode = ab5500_regulator_get_mode, +}; + +static const int ab5500_ldo_lg_voltages[] = { + [0x00] = 1200000, + [0x01] = 0, /* not used */ + [0x02] = 1500000, + [0x03] = 1800000, + [0x04] = 0, /* not used */ + [0x05] = 2500000, + [0x06] = 2730000, + [0x07] = 2910000, +}; + +static const int ab5500_ldo_kh_voltages[] = { + [0x00] = 1200000, + [0x01] = 1500000, + [0x02] = 1800000, + [0x03] = 2100000, + [0x04] = 2500000, + [0x05] = 2750000, + [0x06] = 2790000, + [0x07] = 2910000, +}; + +static const int ab5500_ldo_vdigmic_voltages[] = { + [0x00] = 2100000, +}; + +static const int ab5500_ldo_sim_voltages[] = { + [0x00] = 1875000, + [0x01] = 2800000, + [0x02] = 2900000, +}; + +static const int ab5500_bias2_voltages[] = { + [0x00] = 2000000, + [0x01] = 2200000, +}; + +static struct ab5500_regulator ab5500_regulators[] = { + [AB5500_LDO_L] = { + .desc = { + .name = "LDO_L", + .id = AB5500_LDO_L, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_ldo_lg_voltages) - + 2, + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_L_ST, + .voltages = ab5500_ldo_lg_voltages, + .num_holes = 2, /* 2 register values unused */ + .enable_time = 400, + .mode = AB5500_LDO_MODE_FULLPOWER, + .update_mask = AB5500_LDO_MODE_MASK, + .update_val_normal = AB5500_LDO_MODE_FULLPOWER, + .update_val_idle = AB5500_LDO_MODE_LOWPOWER, + .voltage_mask = AB5500_LDO_VOLT_MASK, + }, + [AB5500_LDO_G] = { + .desc = { + .name = "LDO_G", + .id = AB5500_LDO_G, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_ldo_lg_voltages) - + 2, + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_G_ST, + .voltages = ab5500_ldo_lg_voltages, + .num_holes = 2, /* 2 register values unused */ + .enable_time = 400, + .mode = AB5500_LDO_MODE_FULLPOWER, + .update_mask = AB5500_LDO_MODE_MASK, + .update_val_normal = AB5500_LDO_MODE_FULLPOWER, + .update_val_idle = AB5500_LDO_MODE_LOWPOWER, + .voltage_mask = AB5500_LDO_VOLT_MASK, + }, + [AB5500_LDO_K] = { + .desc = { + .name = "LDO_K", + .id = AB5500_LDO_K, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_ldo_kh_voltages), + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_K_ST, + .voltages = ab5500_ldo_kh_voltages, + .enable_time = 400, + .mode = AB5500_LDO_MODE_FULLPOWER, + .update_mask = AB5500_LDO_MODE_MASK, + .update_val_normal = AB5500_LDO_MODE_FULLPOWER, + .update_val_idle = AB5500_LDO_MODE_LOWPOWER, + .voltage_mask = AB5500_LDO_VOLT_MASK, + }, + [AB5500_LDO_H] = { + .desc = { + .name = "LDO_H", + .id = AB5500_LDO_H, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_ldo_kh_voltages), + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_H_ST, + .voltages = ab5500_ldo_kh_voltages, + .enable_time = 400, + .mode = AB5500_LDO_MODE_FULLPOWER, + .update_mask = AB5500_LDO_MODE_MASK, + .update_val_normal = AB5500_LDO_MODE_FULLPOWER, + .update_val_idle = AB5500_LDO_MODE_LOWPOWER, + .voltage_mask = AB5500_LDO_VOLT_MASK, + }, + [AB5500_LDO_VDIGMIC] = { + .desc = { + .name = "LDO_VDIGMIC", + .id = AB5500_LDO_VDIGMIC, + .ops = &ab5500_regulator_fixed_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = + ARRAY_SIZE(ab5500_ldo_vdigmic_voltages), + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_VDIGMIC_ST, + .voltages = ab5500_ldo_vdigmic_voltages, + .enable_time = 450, + .mode = AB5500_LDO_MODE_FULLPOWER, + .update_mask = AB5500_LDO_MODE_MASK, + .update_val_normal = AB5500_LDO_MODE_FULLPOWER, + .update_val_idle = AB5500_LDO_MODE_LOWPOWER, + .voltage_mask = AB5500_LDO_VOLT_MASK, + }, + [AB5500_LDO_SIM] = { + .desc = { + .name = "LDO_SIM", + .id = AB5500_LDO_SIM, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_ldo_sim_voltages), + }, + .bank = AB5500_BANK_SIM_USBSIM, + .reg = AB5500_SIM_SUP, + .voltages = ab5500_ldo_sim_voltages, + .enable_time = 1000, + .mode = AB5500_LDO_MODE_FULLPOWER, + .update_mask = AB5500_LDO_MODE_MASK, + .update_val_normal = AB5500_LDO_MODE_FULLPOWER, + .update_val_idle = AB5500_LDO_MODE_LOWPOWER, + .voltage_mask = AB5500_LDO_VOLT_MASK, + }, + [AB5500_BIAS2] = { + .desc = { + .name = "BIAS2", + .id = AB5500_BIAS2, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_bias2_voltages), + }, + .bank = AB5500_BANK_AUDIO_HEADSETUSB, + .reg = AB5500_MBIAS2, + .voltages = ab5500_bias2_voltages, + .enable_time = 1000, + .mode = AB5500_MBIAS2_ENABLE, + .update_mask = AB5500_MBIAS2_MODE_MASK, + .update_val_normal = AB5500_MBIAS2_ENABLE, + .update_val_idle = AB5500_MBIAS2_ENABLE, + .voltage_mask = AB5500_MBIAS2_VOLT_MASK, + }, +}; + + +static int __devinit ab5500_regulator_probe(struct platform_device *pdev) +{ + struct ab5500_platform_data *ppdata = pdev->dev.parent->platform_data; + struct ab5500_regulator_platform_data *pdata = ppdata->regulator; + struct ab5500_regulators *ab5500; + int err = 0; + int i; + + if (!pdata || !pdata->regulator) + return -EINVAL; + + ab5500 = kzalloc(sizeof(*ab5500), GFP_KERNEL); + if (!ab5500) + return -ENOMEM; + + ab5500->dev = &pdev->dev; + + platform_set_drvdata(pdev, ab5500); + + for (i = 0; i < AB5500_NUM_REGULATORS; i++) { + struct ab5500_regulator *regulator = &ab5500_regulators[i]; + struct regulator_dev *rdev; + + ab5500->regulator[i] = regulator; + + rdev = regulator_register(®ulator->desc, &pdev->dev, + &pdata->regulator[i], ab5500); + if (IS_ERR(rdev)) { + err = PTR_ERR(rdev); + dev_err(&pdev->dev, "failed to register regulator %s err %d\n", + regulator->desc.name, err); + goto err_unregister; + } + + ab5500->rdev[i] = rdev; + } + + return 0; + +err_unregister: + /* remove the already registered regulators */ + while (--i >= 0) + regulator_unregister(ab5500->rdev[i]); + + platform_set_drvdata(pdev, NULL); + kfree(ab5500); + + return err; +} + +static int __devexit ab5500_regulators_remove(struct platform_device *pdev) +{ + struct ab5500_regulators *ab5500 = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < AB5500_NUM_REGULATORS; i++) + regulator_unregister(ab5500->rdev[i]); + + platform_set_drvdata(pdev, NULL); + kfree(ab5500); + + return 0; +} + +static struct platform_driver ab5500_regulator_driver = { + .driver = { + .name = "ab5500-regulator", + .owner = THIS_MODULE, + }, + .probe = ab5500_regulator_probe, + .remove = __devexit_p(ab5500_regulators_remove), +}; + +static __init int ab5500_regulator_init(void) +{ + return platform_driver_register(&ab5500_regulator_driver); +} + +static __exit void ab5500_regulator_exit(void) +{ + platform_driver_unregister(&ab5500_regulator_driver); +} + +subsys_initcall(ab5500_regulator_init); +module_exit(ab5500_regulator_exit); + +MODULE_DESCRIPTION("AB5500 Regulator Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:ab5500-regulator"); diff --git a/drivers/regulator/ab8500-debug.c b/drivers/regulator/ab8500-debug.c new file mode 100644 index 00000000000..76eff94d136 --- /dev/null +++ b/drivers/regulator/ab8500-debug.c @@ -0,0 +1,1852 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson. + * + * License Terms: GNU General Public License v2 + */ + +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/debugfs.h> +#include <linux/platform_device.h> +#include <linux/kobject.h> +#include <linux/slab.h> +#include <linux/mfd/abx500.h> +#include <linux/regulator/ab8500-debug.h> +#include <linux/io.h> +#include <mach/db8500-regs.h> /* U8500_BACKUPRAM1_BASE */ +#include <mach/hardware.h> + +/* board profile address - to determine if suspend-force is default */ +#define BOOT_INFO_BACKUPRAM1 (U8500_BACKUPRAM1_BASE + 0xffc) +#define BOARD_PROFILE_BACKUPRAM1 (0x3) + +/* board profile option */ +#define OPTION_BOARD_VERSION_V5X 50 + +/* for error prints */ +struct device *dev; +struct platform_device *pdev; + +/* setting for suspend force (disabled by default) */ +static bool setting_suspend_force; + +/* + * regulator states + */ +enum ab8500_regulator_state_id { + AB8500_REGULATOR_STATE_INIT, + AB8500_REGULATOR_STATE_SUSPEND, + AB8500_REGULATOR_STATE_SUSPEND_CORE, + AB8500_REGULATOR_STATE_RESUME_CORE, + AB8500_REGULATOR_STATE_RESUME, + AB8500_REGULATOR_STATE_CURRENT, + NUM_REGULATOR_STATE +}; + +static const char *regulator_state_name[NUM_REGULATOR_STATE] = { + [AB8500_REGULATOR_STATE_INIT] = "init", + [AB8500_REGULATOR_STATE_SUSPEND] = "suspend", + [AB8500_REGULATOR_STATE_SUSPEND_CORE] = "suspend-core", + [AB8500_REGULATOR_STATE_RESUME_CORE] = "resume-core", + [AB8500_REGULATOR_STATE_RESUME] = "resume", + [AB8500_REGULATOR_STATE_CURRENT] = "current", +}; + +/* + * regulator register definitions + */ +enum ab8500_register_id { + AB8500_REGU_NOUSE, /* if not defined */ + AB8500_REGU_REQUEST_CTRL1, + AB8500_REGU_REQUEST_CTRL2, + AB8500_REGU_REQUEST_CTRL3, + AB8500_REGU_REQUEST_CTRL4, + AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + AB8500_REGU_HW_HP_REQ1_VALID1, + AB8500_REGU_HW_HP_REQ1_VALID2, + AB8500_REGU_HW_HP_REQ2_VALID1, + AB8500_REGU_HW_HP_REQ2_VALID2, + AB8500_REGU_SW_HP_REQ_VALID1, + AB8500_REGU_SW_HP_REQ_VALID2, + AB8500_REGU_SYSCLK_REQ1_VALID, + AB8500_REGU_SYSCLK_REQ2_VALID, + AB8500_REGU_MISC1, + AB8500_REGU_OTG_SUPPLY_CTRL, + AB8500_REGU_VUSB_CTRL, + AB8500_REGU_VAUDIO_SUPPLY, + AB8500_REGU_CTRL1_VAMIC, + AB8500_REGU_ARM_REGU1, + AB8500_REGU_ARM_REGU2, + AB8500_REGU_VAPE_REGU, + AB8500_REGU_VSMPS1_REGU, + AB8500_REGU_VSMPS2_REGU, + AB8500_REGU_VSMPS3_REGU, + AB8500_REGU_VPLL_VANA_REGU, + AB8500_REGU_VREF_DDR, + AB8500_REGU_EXT_SUPPLY_REGU, + AB8500_REGU_VAUX12_REGU, + AB8500_REGU_VRF1_VAUX3_REGU, + AB8500_REGU_VARM_SEL1, + AB8500_REGU_VARM_SEL2, + AB8500_REGU_VARM_SEL3, + AB8500_REGU_VAPE_SEL1, + AB8500_REGU_VAPE_SEL2, + AB8500_REGU_VAPE_SEL3, + AB8500_REGU_VBB_SEL1, + AB8500_REGU_VBB_SEL2, + AB8500_REGU_VSMPS1_SEL1, + AB8500_REGU_VSMPS1_SEL2, + AB8500_REGU_VSMPS1_SEL3, + AB8500_REGU_VSMPS2_SEL1, + AB8500_REGU_VSMPS2_SEL2, + AB8500_REGU_VSMPS2_SEL3, + AB8500_REGU_VSMPS3_SEL1, + AB8500_REGU_VSMPS3_SEL2, + AB8500_REGU_VSMPS3_SEL3, + AB8500_REGU_VAUX1_SEL, + AB8500_REGU_VAUX2_SEL, + AB8500_REGU_VRF1_VAUX3_SEL, + AB8500_REGU_CTRL_EXT_SUP, + AB8500_REGU_VMOD_REGU, + AB8500_REGU_VMOD_SEL1, + AB8500_REGU_VMOD_SEL2, + AB8500_REGU_CTRL_DISCH, + AB8500_REGU_CTRL_DISCH2, + AB8500_OTHER_SYSCLK_CTRL, /* Other */ + AB8500_OTHER_VSIM_SYSCLK_CTRL, /* Other */ + AB8500_OTHER_SYSULPCLK_CTRL1, /* Other */ + AB8500_OTHER_TVOUT_CTRL, /* Other */ + NUM_AB8500_REGISTER +}; + +struct ab8500_register { + const char *name; + u8 bank; + u8 addr; +}; + +static struct ab8500_register + ab8500_register[NUM_AB8500_REGISTER] = { + [AB8500_REGU_REQUEST_CTRL1] = { + .name = "ReguRequestCtrl1", + .bank = 0x03, + .addr = 0x03, + }, + [AB8500_REGU_REQUEST_CTRL2] = { + .name = "ReguRequestCtrl2", + .bank = 0x03, + .addr = 0x04, + }, + [AB8500_REGU_REQUEST_CTRL3] = { + .name = "ReguRequestCtrl3", + .bank = 0x03, + .addr = 0x05, + }, + [AB8500_REGU_REQUEST_CTRL4] = { + .name = "ReguRequestCtrl4", + .bank = 0x03, + .addr = 0x06, + }, + [AB8500_REGU_SYSCLK_REQ1_HP_VALID1] = { + .name = "ReguSysClkReq1HPValid", + .bank = 0x03, + .addr = 0x07, + }, + [AB8500_REGU_SYSCLK_REQ1_HP_VALID2] = { + .name = "ReguSysClkReq1HPValid2", + .bank = 0x03, + .addr = 0x08, + }, + [AB8500_REGU_HW_HP_REQ1_VALID1] = { + .name = "ReguHwHPReq1Valid1", + .bank = 0x03, + .addr = 0x09, + }, + [AB8500_REGU_HW_HP_REQ1_VALID2] = { + .name = "ReguHwHPReq1Valid2", + .bank = 0x03, + .addr = 0x0a, + }, + [AB8500_REGU_HW_HP_REQ2_VALID1] = { + .name = "ReguHwHPReq2Valid1", + .bank = 0x03, + .addr = 0x0b, + }, + [AB8500_REGU_HW_HP_REQ2_VALID2] = { + .name = "ReguHwHPReq2Valid2", + .bank = 0x03, + .addr = 0x0c, + }, + [AB8500_REGU_SW_HP_REQ_VALID1] = { + .name = "ReguSwHPReqValid1", + .bank = 0x03, + .addr = 0x0d, + }, + [AB8500_REGU_SW_HP_REQ_VALID2] = { + .name = "ReguSwHPReqValid2", + .bank = 0x03, + .addr = 0x0e, + }, + [AB8500_REGU_SYSCLK_REQ1_VALID] = { + .name = "ReguSysClkReqValid1", + .bank = 0x03, + .addr = 0x0f, + }, + [AB8500_REGU_SYSCLK_REQ2_VALID] = { + .name = "ReguSysClkReqValid2", + .bank = 0x03, + .addr = 0x10, + }, + [AB8500_REGU_MISC1] = { + .name = "ReguMisc1", + .bank = 0x03, + .addr = 0x80, + }, + [AB8500_REGU_OTG_SUPPLY_CTRL] = { + .name = "OTGSupplyCtrl", + .bank = 0x03, + .addr = 0x81, + }, + [AB8500_REGU_VUSB_CTRL] = { + .name = "VusbCtrl", + .bank = 0x03, + .addr = 0x82, + }, + [AB8500_REGU_VAUDIO_SUPPLY] = { + .name = "VaudioSupply", + .bank = 0x03, + .addr = 0x83, + }, + [AB8500_REGU_CTRL1_VAMIC] = { + .name = "ReguCtrl1VAmic", + .bank = 0x03, + .addr = 0x84, + }, + [AB8500_REGU_ARM_REGU1] = { + .name = "ArmRegu1", + .bank = 0x04, + .addr = 0x00, + }, + [AB8500_REGU_ARM_REGU2] = { + .name = "ArmRegu2", + .bank = 0x04, + .addr = 0x01, + }, + [AB8500_REGU_VAPE_REGU] = { + .name = "VapeRegu", + .bank = 0x04, + .addr = 0x02, + }, + [AB8500_REGU_VSMPS1_REGU] = { + .name = "Vsmps1Regu", + .bank = 0x04, + .addr = 0x03, + }, + [AB8500_REGU_VSMPS2_REGU] = { + .name = "Vsmps2Regu", + .bank = 0x04, + .addr = 0x04, + }, + [AB8500_REGU_VSMPS3_REGU] = { + .name = "Vsmps3Regu", + .bank = 0x04, + .addr = 0x05, + }, + [AB8500_REGU_VPLL_VANA_REGU] = { + .name = "VpllVanaRegu", + .bank = 0x04, + .addr = 0x06, + }, + [AB8500_REGU_VREF_DDR] = { + .name = "VrefDDR", + .bank = 0x04, + .addr = 0x07, + }, + [AB8500_REGU_EXT_SUPPLY_REGU] = { + .name = "ExtSupplyRegu", + .bank = 0x04, + .addr = 0x08, + }, + [AB8500_REGU_VAUX12_REGU] = { + .name = "Vaux12Regu", + .bank = 0x04, + .addr = 0x09, + }, + [AB8500_REGU_VRF1_VAUX3_REGU] = { + .name = "VRF1Vaux3Regu", + .bank = 0x04, + .addr = 0x0a, + }, + [AB8500_REGU_VARM_SEL1] = { + .name = "VarmSel1", + .bank = 0x04, + .addr = 0x0b, + }, + [AB8500_REGU_VARM_SEL2] = { + .name = "VarmSel2", + .bank = 0x04, + .addr = 0x0c, + }, + [AB8500_REGU_VARM_SEL3] = { + .name = "VarmSel3", + .bank = 0x04, + .addr = 0x0d, + }, + [AB8500_REGU_VAPE_SEL1] = { + .name = "VapeSel1", + .bank = 0x04, + .addr = 0x0e, + }, + [AB8500_REGU_VAPE_SEL2] = { + .name = "VapeSel2", + .bank = 0x04, + .addr = 0x0f, + }, + [AB8500_REGU_VAPE_SEL3] = { + .name = "VapeSel3", + .bank = 0x04, + .addr = 0x10, + }, + [AB8500_REGU_VBB_SEL1] = { + .name = "VBBSel1", + .bank = 0x04, + .addr = 0x11, + }, + [AB8500_REGU_VBB_SEL2] = { + .name = "VBBSel2", + .bank = 0x04, + .addr = 0x12, + }, + [AB8500_REGU_VSMPS1_SEL1] = { + .name = "Vsmps1Sel1", + .bank = 0x04, + .addr = 0x13, + }, + [AB8500_REGU_VSMPS1_SEL2] = { + .name = "Vsmps1Sel2", + .bank = 0x04, + .addr = 0x14, + }, + [AB8500_REGU_VSMPS1_SEL3] = { + .name = "Vsmps1Sel3", + .bank = 0x04, + .addr = 0x15, + }, + [AB8500_REGU_VSMPS2_SEL1] = { + .name = "Vsmps2Sel1", + .bank = 0x04, + .addr = 0x17, + }, + [AB8500_REGU_VSMPS2_SEL2] = { + .name = "Vsmps2Sel2", + .bank = 0x04, + .addr = 0x18, + }, + [AB8500_REGU_VSMPS2_SEL3] = { + .name = "Vsmps2Sel3", + .bank = 0x04, + .addr = 0x19, + }, + [AB8500_REGU_VSMPS3_SEL1] = { + .name = "Vsmps3Sel1", + .bank = 0x04, + .addr = 0x1b, + }, + [AB8500_REGU_VSMPS3_SEL2] = { + .name = "Vsmps3Sel2", + .bank = 0x04, + .addr = 0x1c, + }, + [AB8500_REGU_VSMPS3_SEL3] = { + .name = "Vsmps3Sel3", + .bank = 0x04, + .addr = 0x1d, + }, + [AB8500_REGU_VAUX1_SEL] = { + .name = "Vaux1Sel", + .bank = 0x04, + .addr = 0x1f, + }, + [AB8500_REGU_VAUX2_SEL] = { + .name = "Vaux2Sel", + .bank = 0x04, + .addr = 0x20, + }, + [AB8500_REGU_VRF1_VAUX3_SEL] = { + .name = "VRF1Vaux3Sel", + .bank = 0x04, + .addr = 0x21, + }, + [AB8500_REGU_CTRL_EXT_SUP] = { + .name = "ReguCtrlExtSup", + .bank = 0x04, + .addr = 0x22, + }, + [AB8500_REGU_VMOD_REGU] = { + .name = "VmodRegu", + .bank = 0x04, + .addr = 0x40, + }, + [AB8500_REGU_VMOD_SEL1] = { + .name = "VmodSel1", + .bank = 0x04, + .addr = 0x41, + }, + [AB8500_REGU_VMOD_SEL2] = { + .name = "VmodSel2", + .bank = 0x04, + .addr = 0x42, + }, + [AB8500_REGU_CTRL_DISCH] = { + .name = "ReguCtrlDisch", + .bank = 0x04, + .addr = 0x43, + }, + [AB8500_REGU_CTRL_DISCH2] = { + .name = "ReguCtrlDisch2", + .bank = 0x04, + .addr = 0x44, + }, + /* Outside regulator banks */ + [AB8500_OTHER_SYSCLK_CTRL] = { + .name = "SysClkCtrl", + .bank = 0x02, + .addr = 0x0c, + }, + [AB8500_OTHER_VSIM_SYSCLK_CTRL] = { + .name = "VsimSysClkCtrl", + .bank = 0x02, + .addr = 0x33, + }, + [AB8500_OTHER_SYSULPCLK_CTRL1] = { + .name = "SysUlpClkCtrl1", + .bank = 0x02, + .addr = 0x0b, + }, + [AB8500_OTHER_TVOUT_CTRL] = { + .name = "TVoutCtrl", + .bank = 0x06, + .addr = 0x80, + }, +}; + +static u8 ab8500_register_state[NUM_REGULATOR_STATE][NUM_AB8500_REGISTER]; +static bool ab8500_register_state_saved[NUM_REGULATOR_STATE]; +static bool ab8500_register_state_save = true; + +static int ab8500_regulator_record_state(int state) +{ + u8 val; + int i; + int ret; + + /* check arguments */ + if ((state > NUM_REGULATOR_STATE) || (state < 0)) { + dev_err(dev, "Wrong state specified\n"); + return -EINVAL; + } + + /* record */ + if (!ab8500_register_state_save) + goto exit; + + ab8500_register_state_saved[state] = true; + + for (i = 1; i < NUM_AB8500_REGISTER; i++) { + ret = abx500_get_register_interruptible(dev, + ab8500_register[i].bank, + ab8500_register[i].addr, + &val); + if (ret < 0) { + dev_err(dev, "abx500_get_reg fail %d, %d\n", + ret, __LINE__); + return -EINVAL; + } + + ab8500_register_state[state][i] = val; + } +exit: + return 0; +} + +/* + * regulator register dump + */ +static int ab8500_regulator_dump_print(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + int state, reg_id, i; + int err; + + /* record current state */ + ab8500_regulator_record_state(AB8500_REGULATOR_STATE_CURRENT); + + /* print dump header */ + err = seq_printf(s, "ab8500-regulator dump:\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow\n"); + + /* print states */ + for (state = NUM_REGULATOR_STATE - 1; state >= 0; state--) { + if (ab8500_register_state_saved[state]) + err = seq_printf(s, "%16s saved -------", + regulator_state_name[state]); + else + err = seq_printf(s, "%12s not saved -------", + regulator_state_name[state]); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + + for (i = 0; i < NUM_REGULATOR_STATE; i++) { + if (i < state) + err = seq_printf(s, "-----"); + else if (i == state) + err = seq_printf(s, "----+"); + else + err = seq_printf(s, " |"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", + __LINE__); + } + err = seq_printf(s, "\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + } + + /* print labels */ + err = seq_printf(s, "\n addr\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + + /* dump registers */ + for (reg_id = 1; reg_id < NUM_AB8500_REGISTER; reg_id++) { + err = seq_printf(s, "%22s 0x%02x%02x:", + ab8500_register[reg_id].name, + ab8500_register[reg_id].bank, + ab8500_register[reg_id].addr); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + reg_id, __LINE__); + + for (state = 0; state < NUM_REGULATOR_STATE; state++) { + err = seq_printf(s, " 0x%02x", + ab8500_register_state[state][reg_id]); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + reg_id, __LINE__); + } + + err = seq_printf(s, "\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + reg_id, __LINE__); + } + + return 0; +} + +static int ab8500_regulator_dump_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_regulator_dump_print, inode->i_private); +} + +static const struct file_operations ab8500_regulator_dump_fops = { + .open = ab8500_regulator_dump_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +/* + * regulator status print + */ +enum ab8500_regulator_id { + AB8500_VARM, + AB8500_VBBP, + AB8500_VBBN, + AB8500_VAPE, + AB8500_VSMPS1, + AB8500_VSMPS2, + AB8500_VSMPS3, + AB8500_VPLL, + AB8500_VREFDDR, + AB8500_VMOD, + AB8500_VEXTSUPPLY1, + AB8500_VEXTSUPPLY2, + AB8500_VEXTSUPPLY3, + AB8500_VRF1, + AB8500_VANA, + AB8500_VAUX1, + AB8500_VAUX2, + AB8500_VAUX3, + AB8500_VINTCORE, + AB8500_VTVOUT, + AB8500_VAUDIO, + AB8500_VANAMIC1, + AB8500_VANAMIC2, + AB8500_VDMIC, + AB8500_VUSB, + AB8500_VOTG, + AB8500_VBUSBIS, + AB8500_NUM_REGULATORS, +}; + +/* + * regulator_voltage + */ +struct regulator_volt { + u8 value; + int volt; +}; + +struct regulator_volt_range { + struct regulator_volt start; + struct regulator_volt step; + struct regulator_volt end; +}; + +/* + * ab8500_regulator + * @name + * @update_regid + * @update_mask + * @update_val[4] {off, on, hw, lp} + * @hw_mode_regid + * @hw_mode_mask + * @hw_mode_val[4] {hp/lp, hp/off, hp, hp} + * @hw_valid_regid[4] {sysclkreq1, hw1, hw2, sw} + * @hw_valid_mask[4] {sysclkreq1, hw1, hw2, sw} + * @vsel_sel_regid + * @vsel_sel_mask + * @vsel_val[333] {sel1, sel2, sel3, sel3} + * @vsel_regid + * @vsel_mask + * @vsel_range + * @vsel_range_len + */ +struct ab8500_regulator { + const char *name; + int update_regid; + u8 update_mask; + u8 update_val[4]; + int hw_mode_regid; + u8 hw_mode_mask; + u8 hw_mode_val[4]; + int hw_valid_regid[4]; + u8 hw_valid_mask[4]; + int vsel_sel_regid; + u8 vsel_sel_mask; + u8 vsel_sel_val[4]; + int vsel_regid[3]; + u8 vsel_mask[3]; + struct regulator_volt_range const *vsel_range[3]; + int vsel_range_len[3]; +}; + +static const char *update_val_name[] = { + "off", + "on ", + "hw ", + "lp ", + " - " /* undefined value */ +}; + +static const char *hw_mode_val_name[] = { + "hp/lp ", + "hp/off", + "hp ", + "hp ", + "-/- ", /* undefined value */ +}; + +/* voltage selection */ +static const struct regulator_volt_range varm_vape_vmod_vsel[] = { + { {0x00, 700000}, {0x01, 12500}, {0x35, 1362500} }, + { {0x36, 1362500}, {0x01, 0}, {0x3f, 1362500} }, +}; + +static const struct regulator_volt_range vbbp_vsel[] = { + { {0x00, 0}, {0x10, 100000}, {0x40, 400000} }, + { {0x50, 400000}, {0x10, 0}, {0x70, 400000} }, + { {0x80, -400000}, {0x10, 0}, {0xb0, -400000} }, + { {0xc0, -400000}, {0x10, 100000}, {0xf0, -100000} }, +}; + +static const struct regulator_volt_range vbbn_vsel[] = { + { {0x00, 0}, {0x01, -100000}, {0x04, -400000} }, + { {0x05, -400000}, {0x01, 0}, {0x07, -400000} }, + { {0x08, 0}, {0x01, 100000}, {0x0c, 400000} }, + { {0x0d, 400000}, {0x01, 0}, {0x0f, 400000} }, +}; + +static const struct regulator_volt_range vsmps1_vsel[] = { + { {0x00, 1100000}, {0x01, 0}, {0x1f, 1100000} }, + { {0x20, 1100000}, {0x01, 12500}, {0x30, 1300000} }, + { {0x31, 1300000}, {0x01, 0}, {0x3f, 1300000} }, +}; + +static const struct regulator_volt_range vsmps2_vsel[] = { + { {0x00, 1800000}, {0x01, 0}, {0x38, 1800000} }, + { {0x39, 1800000}, {0x01, 12500}, {0x7f, 1875000} }, +}; + +static const struct regulator_volt_range vsmps3_vsel[] = { + { {0x00, 700000}, {0x01, 12500}, {0x35, 1363500} }, + { {0x36, 1363500}, {0x01, 0}, {0x7f, 1363500} }, +}; + +static const struct regulator_volt_range vaux1_vaux2_vsel[] = { + { {0x00, 1100000}, {0x01, 100000}, {0x04, 1500000} }, + { {0x05, 1800000}, {0x01, 50000}, {0x07, 1900000} }, + { {0x08, 2500000}, {0x01, 0}, {0x08, 2500000} }, + { {0x09, 2650000}, {0x01, 50000}, {0x0c, 2800000} }, + { {0x0d, 2900000}, {0x01, 100000}, {0x0e, 3000000} }, + { {0x0f, 3300000}, {0x01, 0}, {0x0f, 3300000} }, +}; + +static const struct regulator_volt_range vaux3_vsel[] = { + { {0x00, 1200000}, {0x01, 300000}, {0x03, 2100000} }, + { {0x04, 2500000}, {0x01, 250000}, {0x05, 2750000} }, + { {0x06, 2790000}, {0x01, 0}, {0x06, 2790000} }, + { {0x07, 2910000}, {0x01, 0}, {0x07, 2910000} }, +}; + +static const struct regulator_volt_range vrf1_vsel[] = { + { {0x00, 1800000}, {0x10, 200000}, {0x10, 2000000} }, + { {0x20, 2150000}, {0x10, 0}, {0x20, 2150000} }, + { {0x30, 2500000}, {0x10, 0}, {0x30, 2500000} }, +}; + +static const struct regulator_volt_range vintcore12_vsel[] = { + { {0x00, 1200000}, {0x08, 25000}, {0x30, 1350000} }, + { {0x38, 1350000}, {0x01, 0}, {0x38, 1350000} }, +}; + +/* regulators */ +static struct ab8500_regulator ab8500_regulator[AB8500_NUM_REGULATORS] = { + [AB8500_VARM] = { + .name = "Varm", + .update_regid = AB8500_REGU_ARM_REGU1, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL1, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x02, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x02, + .vsel_sel_regid = AB8500_REGU_ARM_REGU1, + .vsel_sel_mask = 0x0c, + .vsel_sel_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VARM_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = varm_vape_vmod_vsel, + .vsel_range_len[0] = ARRAY_SIZE(varm_vape_vmod_vsel), + .vsel_regid[1] = AB8500_REGU_VARM_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = varm_vape_vmod_vsel, + .vsel_range_len[1] = ARRAY_SIZE(varm_vape_vmod_vsel), + .vsel_regid[2] = AB8500_REGU_VARM_SEL3, + .vsel_mask[2] = 0x3f, + .vsel_range[2] = varm_vape_vmod_vsel, + .vsel_range_len[2] = ARRAY_SIZE(varm_vape_vmod_vsel), + }, + [AB8500_VBBP] = { + .name = "Vbbp", + .update_regid = AB8500_REGU_ARM_REGU2, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x00}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x04, + .vsel_sel_regid = AB8500_REGU_ARM_REGU1, + .vsel_sel_mask = 0x10, + .vsel_sel_val = {0x00, 0x10, 0x00, 0x00}, + .vsel_regid[0] = AB8500_REGU_VBB_SEL1, + .vsel_mask[0] = 0xf0, + .vsel_range[0] = vbbp_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vbbp_vsel), + .vsel_regid[1] = AB8500_REGU_VBB_SEL2, + .vsel_mask[1] = 0xf0, + .vsel_range[1] = vbbp_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vbbp_vsel), + }, + [AB8500_VBBN] = { + .name = "Vbbn", + .update_regid = AB8500_REGU_ARM_REGU2, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x00}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x04, + .vsel_sel_regid = AB8500_REGU_ARM_REGU1, + .vsel_sel_mask = 0x20, + .vsel_sel_val = {0x00, 0x20, 0x00, 0x00}, + .vsel_regid[0] = AB8500_REGU_VBB_SEL1, + .vsel_mask[0] = 0x0f, + .vsel_range[0] = vbbn_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vbbn_vsel), + .vsel_regid[1] = AB8500_REGU_VBB_SEL2, + .vsel_mask[1] = 0x0f, + .vsel_range[1] = vbbn_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vbbn_vsel), + }, + [AB8500_VAPE] = { + .name = "Vape", + .update_regid = AB8500_REGU_VAPE_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL1, + .hw_mode_mask = 0x0c, + .hw_mode_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x01, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x01, + .vsel_sel_regid = AB8500_REGU_VAPE_REGU, + .vsel_sel_mask = 0x24, + .vsel_sel_val = {0x00, 0x04, 0x20, 0x24}, + .vsel_regid[0] = AB8500_REGU_VAPE_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = varm_vape_vmod_vsel, + .vsel_range_len[0] = ARRAY_SIZE(varm_vape_vmod_vsel), + .vsel_regid[1] = AB8500_REGU_VAPE_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = varm_vape_vmod_vsel, + .vsel_range_len[1] = ARRAY_SIZE(varm_vape_vmod_vsel), + .vsel_regid[2] = AB8500_REGU_VAPE_SEL3, + .vsel_mask[2] = 0x3f, + .vsel_range[2] = varm_vape_vmod_vsel, + .vsel_range_len[2] = ARRAY_SIZE(varm_vape_vmod_vsel), + }, + [AB8500_VSMPS1] = { + .name = "Vsmps1", + .update_regid = AB8500_REGU_VSMPS1_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL1, + .hw_mode_mask = 0x30, + .hw_mode_val = {0x00, 0x10, 0x20, 0x30}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x01, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x01, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x01, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x04, + .vsel_sel_regid = AB8500_REGU_VSMPS1_REGU, + .vsel_sel_mask = 0x0c, + .vsel_sel_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VSMPS1_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = vsmps1_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vsmps1_vsel), + .vsel_regid[1] = AB8500_REGU_VSMPS1_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = vsmps1_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vsmps1_vsel), + .vsel_regid[2] = AB8500_REGU_VSMPS1_SEL3, + .vsel_mask[2] = 0x3f, + .vsel_range[2] = vsmps1_vsel, + .vsel_range_len[2] = ARRAY_SIZE(vsmps1_vsel), + }, + [AB8500_VSMPS2] = { + .name = "Vsmps2", + .update_regid = AB8500_REGU_VSMPS2_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL1, + .hw_mode_mask = 0xc0, + .hw_mode_val = {0x00, 0x40, 0x80, 0xc0}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x02, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x02, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x02, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x08, + .vsel_sel_regid = AB8500_REGU_VSMPS2_REGU, + .vsel_sel_mask = 0x0c, + .vsel_sel_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VSMPS2_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = vsmps2_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vsmps2_vsel), + .vsel_regid[1] = AB8500_REGU_VSMPS2_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = vsmps2_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vsmps2_vsel), + .vsel_regid[2] = AB8500_REGU_VSMPS2_SEL3, + .vsel_mask[2] = 0x3f, + .vsel_range[2] = vsmps2_vsel, + .vsel_range_len[2] = ARRAY_SIZE(vsmps2_vsel), + }, + [AB8500_VSMPS3] = { + .name = "Vsmps3", + .update_regid = AB8500_REGU_VSMPS3_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL2, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x04, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x04, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x04, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x10, + .vsel_sel_regid = AB8500_REGU_VSMPS3_REGU, + .vsel_sel_mask = 0x0c, + .vsel_sel_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VSMPS3_SEL1, + .vsel_mask[0] = 0x7f, + .vsel_range[0] = vsmps3_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vsmps3_vsel), + .vsel_regid[1] = AB8500_REGU_VSMPS3_SEL2, + .vsel_mask[1] = 0x7f, + .vsel_range[1] = vsmps3_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vsmps3_vsel), + .vsel_regid[2] = AB8500_REGU_VSMPS3_SEL3, + .vsel_mask[2] = 0x7f, + .vsel_range[2] = vsmps3_vsel, + .vsel_range_len[2] = ARRAY_SIZE(vsmps3_vsel), + }, + [AB8500_VPLL] = { + .name = "Vpll", + .update_regid = AB8500_REGU_VPLL_VANA_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL2, + .hw_mode_mask = 0x0c, + .hw_mode_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x10, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x10, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x10, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x40, + }, + [AB8500_VREFDDR] = { + .name = "VrefDDR", + .update_regid = AB8500_REGU_VREF_DDR, + .update_mask = 0x01, + .update_val = {0x00, 0x01, 0x00, 0x00}, + }, + [AB8500_VMOD] = { + .name = "Vmod", + .update_regid = AB8500_REGU_VMOD_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_VMOD_REGU, + .hw_mode_mask = 0xc0, + .hw_mode_val = {0x00, 0x40, 0x80, 0xc0}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x08, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID2, + .hw_valid_mask[1] = 0x08, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID2, + .hw_valid_mask[2] = 0x08, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x20, + .vsel_sel_regid = AB8500_REGU_VMOD_REGU, + .vsel_sel_mask = 0x04, + .vsel_sel_val = {0x00, 0x04, 0x00, 0x00}, + .vsel_regid[0] = AB8500_REGU_VMOD_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = varm_vape_vmod_vsel, + .vsel_range_len[0] = ARRAY_SIZE(varm_vape_vmod_vsel), + .vsel_regid[1] = AB8500_REGU_VMOD_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = varm_vape_vmod_vsel, + .vsel_range_len[1] = ARRAY_SIZE(varm_vape_vmod_vsel), + }, + [AB8500_VEXTSUPPLY1] = { + .name = "Vextsupply1", + .update_regid = AB8500_REGU_EXT_SUPPLY_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL2, + .hw_mode_mask = 0xc0, + .hw_mode_val = {0x00, 0x40, 0x80, 0xc0}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x10, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID2, + .hw_valid_mask[1] = 0x01, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID2, + .hw_valid_mask[2] = 0x01, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x04, + }, + [AB8500_VEXTSUPPLY2] = { + .name = "VextSupply2", + .update_regid = AB8500_REGU_EXT_SUPPLY_REGU, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL3, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x20, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID2, + .hw_valid_mask[1] = 0x02, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID2, + .hw_valid_mask[2] = 0x02, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x08, + }, + [AB8500_VEXTSUPPLY3] = { + .name = "VextSupply3", + .update_regid = AB8500_REGU_EXT_SUPPLY_REGU, + .update_mask = 0x30, + .update_val = {0x00, 0x10, 0x20, 0x30}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL3, + .hw_mode_mask = 0x0c, + .hw_mode_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x40, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID2, + .hw_valid_mask[1] = 0x04, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID2, + .hw_valid_mask[2] = 0x04, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x10, + }, + [AB8500_VRF1] = { + .name = "Vrf1", + .update_regid = AB8500_REGU_VRF1_VAUX3_REGU, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VRF1_VAUX3_SEL, + .vsel_mask[0] = 0x30, + .vsel_range[0] = vrf1_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vrf1_vsel), + }, + [AB8500_VANA] = { + .name = "Vana", + .update_regid = AB8500_REGU_VPLL_VANA_REGU, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL2, + .hw_mode_mask = 0x30, + .hw_mode_val = {0x00, 0x10, 0x20, 0x30}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x08, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x08, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x08, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x20, + }, + [AB8500_VAUX1] = { + .name = "Vaux1", + .update_regid = AB8500_REGU_VAUX12_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL3, + .hw_mode_mask = 0x30, + .hw_mode_val = {0x00, 0x10, 0x20, 0x30}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x20, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x20, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x20, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x80, + .vsel_regid[0] = AB8500_REGU_VAUX1_SEL, + .vsel_mask[0] = 0x0f, + .vsel_range[0] = vaux1_vaux2_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vaux1_vaux2_vsel), + }, + [AB8500_VAUX2] = { + .name = "Vaux2", + .update_regid = AB8500_REGU_VAUX12_REGU, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL3, + .hw_mode_mask = 0xc0, + .hw_mode_val = {0x00, 0x40, 0x80, 0xc0}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x40, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x40, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x40, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x01, + .vsel_regid[0] = AB8500_REGU_VAUX2_SEL, + .vsel_mask[0] = 0x0f, + .vsel_range[0] = vaux1_vaux2_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vaux1_vaux2_vsel), + }, + [AB8500_VAUX3] = { + .name = "Vaux3", + .update_regid = AB8500_REGU_VRF1_VAUX3_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL4, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x80, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x80, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x80, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x02, + .vsel_regid[0] = AB8500_REGU_VRF1_VAUX3_SEL, + .vsel_mask[0] = 0x07, + .vsel_range[0] = vaux3_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vaux3_vsel), + }, + [AB8500_VINTCORE] = { + .name = "VintCore12", + .update_regid = AB8500_REGU_MISC1, + .update_mask = 0x44, + .update_val = {0x00, 0x04, 0x00, 0x44}, + .vsel_regid[0] = AB8500_REGU_MISC1, + .vsel_mask[0] = 0x38, + .vsel_range[0] = vintcore12_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vintcore12_vsel), + }, + [AB8500_VTVOUT] = { + .name = "VTVout", + .update_regid = AB8500_REGU_MISC1, + .update_mask = 0x82, + .update_val = {0x00, 0x02, 0x00, 0x82}, + }, + [AB8500_VAUDIO] = { + .name = "Vaudio", + .update_regid = AB8500_REGU_VAUDIO_SUPPLY, + .update_mask = 0x02, + .update_val = {0x00, 0x02, 0x00, 0x00}, + }, + [AB8500_VANAMIC1] = { + .name = "Vanamic1", + .update_regid = AB8500_REGU_VAUDIO_SUPPLY, + .update_mask = 0x08, + .update_val = {0x00, 0x08, 0x00, 0x00}, + }, + [AB8500_VANAMIC2] = { + .name = "Vanamic2", + .update_regid = AB8500_REGU_VAUDIO_SUPPLY, + .update_mask = 0x10, + .update_val = {0x00, 0x10, 0x00, 0x00}, + }, + [AB8500_VDMIC] = { + .name = "Vdmic", + .update_regid = AB8500_REGU_VAUDIO_SUPPLY, + .update_mask = 0x04, + .update_val = {0x00, 0x04, 0x00, 0x00}, + }, + [AB8500_VUSB] = { + .name = "Vusb", + .update_regid = AB8500_REGU_VUSB_CTRL, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x00, 0x03}, + }, + [AB8500_VOTG] = { + .name = "VOTG", + .update_regid = AB8500_REGU_OTG_SUPPLY_CTRL, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x00, 0x03}, + }, + [AB8500_VBUSBIS] = { + .name = "Vbusbis", + .update_regid = AB8500_REGU_OTG_SUPPLY_CTRL, + .update_mask = 0x08, + .update_val = {0x00, 0x08, 0x00, 0x00}, + }, +}; + +static int status_state; + +static int _get_voltage(struct regulator_volt_range const *volt_range, + u8 value, int *volt) +{ + u8 start = volt_range->start.value; + u8 end = volt_range->end.value; + u8 step = volt_range->step.value; + + /* Check if witin range */ + if (step == 0) { + if (value == start) { + *volt = volt_range->start.volt; + return 1; + } + } else { + if ((start <= value) && (value <= end)) { + if ((value - start)%step != 0) + return -EINVAL; /* invalid setting */ + *volt = volt_range->start.volt + + volt_range->step.volt + *((value - start)/step); + return 1; + } + } + + return 0; +} + +static int get_voltage(struct regulator_volt_range const *volt_range, + int volt_range_len, + u8 value) +{ + int volt; + int i, ret; + + for (i = 0; i < volt_range_len; i++) { + ret = _get_voltage(&volt_range[i], value, &volt); + if (ret < 0) + break; /* invalid setting */ + if (ret == 1) + return volt; /* successful */ + } + + return -EINVAL; +} + +static int ab8500_regulator_status_print(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + int id, regid; + int i; + u8 val; + int err; + + /* record current state */ + ab8500_regulator_record_state(AB8500_REGULATOR_STATE_CURRENT); + + /* check if chosen state is recorded */ + if (!ab8500_register_state_saved[status_state]) { + seq_printf(s, "ab8500-regulator status is not recorded.\n"); + goto exit; + } + + /* print dump header */ + err = seq_printf(s, "ab8500-regulator status:\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow\n"); + + /* print state */ + err = seq_printf(s, "%12s\n", + regulator_state_name[status_state]); + if (err < 0) + dev_err(dev, "seq_printf overflow\n"); + + /* print labels */ + err = seq_printf(s, + "+-----------+----+--------------+-------------------------+\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "| name|man |auto |voltage |\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "+-----------+----+--------------+ +-----------------------+\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "| |mode|mode |0|1|2|3| | 1 | 2 | 3 |\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "+-----------+----+------+-+-+-+-+-+-------+-------+-------+\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + + /* dump registers */ + for (id = 0; id < AB8500_NUM_REGULATORS; id++) { + /* print name */ + err = seq_printf(s, "|%11s|", + ab8500_regulator[id].name); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + id, __LINE__); + + /* print manual mode */ + regid = ab8500_regulator[id].update_regid; + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].update_mask; + for (i = 0; i < 4; i++) { + if (val == ab8500_regulator[id].update_val[i]) + break; + } + err = seq_printf(s, "%4s|", + update_val_name[i]); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + id, __LINE__); + + /* print auto mode */ + regid = ab8500_regulator[id].hw_mode_regid; + if (regid) { + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].hw_mode_mask; + for (i = 0; i < 4; i++) { + if (val == ab8500_regulator[id].hw_mode_val[i]) + break; + } + err = seq_printf(s, "%6s|", + hw_mode_val_name[i]); + } else { + err = seq_printf(s, " |"); + } + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + id, __LINE__); + + /* print valid bits */ + for (i = 0; i < 4; i++) { + regid = ab8500_regulator[id].hw_valid_regid[i]; + if (regid) { + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].hw_valid_mask[i]; + if (val) + err = seq_printf(s, "1|"); + else + err = seq_printf(s, "0|"); + } else { + err = seq_printf(s, " |"); + } + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + regid, __LINE__); + } + + /* print voltage selection */ + regid = ab8500_regulator[id].vsel_sel_regid; + if (regid) { + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].vsel_sel_mask; + for (i = 0; i < 3; i++) { + if (val == ab8500_regulator[id].vsel_sel_val[i]) + break; + } + if (i < 3) + seq_printf(s, "%i|", i + 1); + else + seq_printf(s, "-|"); + } else { + seq_printf(s, " |"); + } + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + regid, __LINE__); + + for (i = 0; i < 3; i++) { + int volt; + + regid = ab8500_regulator[id].vsel_regid[i]; + if (regid) { + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].vsel_mask[i]; + volt = get_voltage( + ab8500_regulator[id].vsel_range[i], + ab8500_regulator[id].vsel_range_len[i], + val); + seq_printf(s, "%7i|", volt); + } else { + seq_printf(s, " |"); + } + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + regid, __LINE__); + } + + err = seq_printf(s, "\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + regid, __LINE__); + + } + err = seq_printf(s, + "+-----------+----+------+-+-+-+-+-+-------+-------+-------+\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "Note! In HW mode, voltage selection is controlled by HW.\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + + +exit: + return 0; +} + +static int ab8500_regulator_status_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + + /* copy user data */ + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + /* convert */ + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + + /* set suspend force setting */ + if (user_val > NUM_REGULATOR_STATE) { + dev_err(dev, "debugfs error input > number of states\n"); + return -EINVAL; + } + + status_state = user_val; + + return buf_size; +} + + +static int ab8500_regulator_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_regulator_status_print, + inode->i_private); +} + +static const struct file_operations ab8500_regulator_status_fops = { + .open = ab8500_regulator_status_open, + .write = ab8500_regulator_status_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +#ifdef CONFIG_PM + +struct ab8500_force_reg { + char *name; + u8 bank; + u8 addr; + u8 mask; + u8 val; + bool restore; + u8 restore_val; +}; + +static struct ab8500_force_reg ab8500_force_reg[] = { + { + /* + * SysClkCtrl + * OTP: 0x00, HSI: 0x06, suspend: 0x00/0x07 (value/mask) + * [ 2] USBClkEna = disable SysClk path to USB block + * [ 1] TVoutClkEna = disable 27Mhz clock to TVout block + * [ 0] TVoutPllEna = disable TVout pll + * (generate 27Mhz from SysClk) + */ + .name = "SysClkCtrl", + .bank = 0x02, + .addr = 0x0c, + .mask = 0x07, + .val = 0x00, + }, + { + /* + * ReguSysClkReq1HPValid2 + * OTP: 0x03, HSI: 0x40, suspend: 0x60/0x70 (value/mask) + * [ 5] VextSupply2SysClkReq1HPValid = Vext2 set by SysClkReq1 + */ + .name = "ReguSysClkReq1HPValid2", + .bank = 0x03, + .addr = 0x08, + .mask = 0x20, /* test and compare with 0x7f */ + .val = 0x20, + }, + { + /* + * ReguRequestCtrl3 + * OTP: 0x00, HSI: 0x00, suspend: 0x05/0x0f (value/mask) + * [1:0] VExtSupply2RequestCtrl[1:0] = VExt2 set in HP/OFF mode + */ + .name = "ReguRequestCtrl3", + .bank = 0x03, + .addr = 0x05, + .mask = 0x03, /* test and compare with 0xff */ + .val = 0x01, + }, + { + /* + * VsimSysClkCtrl + * OTP: 0x01, HSI: 0x21, suspend: 0x01/0xff (value/mask) + * [ 7] VsimSysClkReq8Valid = no connection + * [ 6] VsimSysClkReq7Valid = no connection + * [ 5] VsimSysClkReq6Valid = no connection + * [ 4] VsimSysClkReq5Valid = no connection + * [ 3] VsimSysClkReq4Valid = no connection + * [ 2] VsimSysClkReq3Valid = no connection + * [ 1] VsimSysClkReq2Valid = no connection + * [ 0] VsimSysClkReq1Valid = Vsim set by SysClkReq1 + */ + .name = "VsimSysClkCtrl", + .bank = 0x02, + .addr = 0x33, + .mask = 0xff, + .val = 0x01, + }, + { + /* + * SysUlpClkCtrl1 + * OTP: 0x00, HSI: 0x00, suspend: 0x00/0x0f (value/mask) + * [ 3] 4500SysClkReq = inactive + * [ 2] UlpClkReq = inactive + * [1:0] SysUlpClkIntSel[1:0] = no internal clock switching. + * Internal clock is SysClk. + */ + .name = "SysUlpClkCtrl1", + .bank = 0x02, + .addr = 0x0b, + .mask = 0x0f, + .val = 0x00, + }, + { + /* + * ExtSupplyRegu (HSI: 0x2a on v2-v40?) + * OTP: 0x15, HSI: 0x28, suspend: 0x28/0x3f (value/mask) + * [3:2] VExtSupply2Regu[1:0] = 10 = Vext2 in HW control + * [1:0] VExtSupply1Regu[1:0] = 00 = Vext1 off + */ + .name = "ExtSupplyRegu", + .bank = 0x04, + .addr = 0x08, + .mask = 0x0f, + .val = 0x08, + }, + { + /* + * TVoutCtrl + * OTP: N/A, HSI: N/A, suspend: 0x00/0x03 (value/mask) + * [ 2] PlugTvOn = plug/unplug detection disabled + * [1:0] TvoutDacCtrl[1:0] = "0" forced on DAC input (test) + */ + .name = "TVoutCtrl", + .bank = 0x06, + .addr = 0x80, + .mask = 0x03, + .val = 0x00, + }, +}; + +void ab8500_regulator_debug_force(void) +{ + int ret, i; + + /* save state of registers */ + ret = ab8500_regulator_record_state(AB8500_REGULATOR_STATE_SUSPEND); + if (ret < 0) + dev_err(&pdev->dev, "Failed to record suspend state.\n"); + + /* check if registers should be forced */ + if (!setting_suspend_force) + goto exit; + + /* + * Optimize href v2_v50_pwr board for ApSleep/ApDeepSleep + * power consumption measurements + */ + + for (i = 0; i < ARRAY_SIZE(ab8500_force_reg); i++) { + dev_vdbg(&pdev->dev, "Save and set %s: " + "0x%02x, 0x%02x, 0x%02x, 0x%02x.\n", + ab8500_force_reg[i].name, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + ab8500_force_reg[i].mask, + ab8500_force_reg[i].val); + + /* assume that register should be restored */ + ab8500_force_reg[i].restore = true; + + /* get register value before forcing it */ + ret = abx500_get_register_interruptible(&pdev->dev, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + &ab8500_force_reg[i].restore_val); + if (ret < 0) { + dev_err(dev, "Failed to read %s.\n", + ab8500_force_reg[i].name); + ab8500_force_reg[i].restore = false; + break; + } + + /* force register value */ + ret = abx500_mask_and_set_register_interruptible(&pdev->dev, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + ab8500_force_reg[i].mask, + ab8500_force_reg[i].val); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to write %s.\n", + ab8500_force_reg[i].name); + ab8500_force_reg[i].restore = false; + } + } + +exit: + /* save state of registers */ + ret = ab8500_regulator_record_state( + AB8500_REGULATOR_STATE_SUSPEND_CORE); + if (ret < 0) + dev_err(&pdev->dev, "Failed to record suspend state.\n"); + + return; +} + +void ab8500_regulator_debug_restore(void) +{ + int ret, i; + + /* save state of registers */ + ret = ab8500_regulator_record_state(AB8500_REGULATOR_STATE_RESUME_CORE); + if (ret < 0) + dev_err(&pdev->dev, "Failed to record resume state.\n"); + for (i = ARRAY_SIZE(ab8500_force_reg) - 1; i >= 0; i--) { + /* restore register value */ + if (ab8500_force_reg[i].restore) { + ret = abx500_mask_and_set_register_interruptible( + &pdev->dev, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + ab8500_force_reg[i].mask, + ab8500_force_reg[i].restore_val); + if (ret < 0) + dev_err(&pdev->dev, "Failed to restore %s.\n", + ab8500_force_reg[i].name); + dev_vdbg(&pdev->dev, "Restore %s: " + "0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + ab8500_force_reg[i].name, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + ab8500_force_reg[i].mask, + ab8500_force_reg[i].restore_val); + } + } + + /* save state of registers */ + ret = ab8500_regulator_record_state(AB8500_REGULATOR_STATE_RESUME); + if (ret < 0) + dev_err(&pdev->dev, "Failed to record resume state.\n"); + + return; +} + +#endif + +static int ab8500_regulator_suspend_force_show(struct seq_file *s, void *p) +{ + /* print suspend standby status */ + if (setting_suspend_force) + return seq_printf(s, "suspend force enabled\n"); + else + return seq_printf(s, "no suspend force\n"); +} + +static int ab8500_regulator_suspend_force_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + + /* copy user data */ + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + /* convert */ + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + + /* set suspend force setting */ + if (user_val > 1) { + dev_err(dev, "debugfs error input > 1\n"); + return -EINVAL; + } + + if (user_val) + setting_suspend_force = true; + else + setting_suspend_force = false; + + return buf_size; +} + +static int ab8500_regulator_suspend_force_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_regulator_suspend_force_show, + inode->i_private); +} + +static const struct file_operations ab8500_regulator_suspend_force_fops = { + .open = ab8500_regulator_suspend_force_open, + .write = ab8500_regulator_suspend_force_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static struct dentry *ab8500_regulator_dir; +static struct dentry *ab8500_regulator_dump_file; +static struct dentry *ab8500_regulator_status_file; +static struct dentry *ab8500_regulator_suspend_force_file; + +static int __devinit ab8500_regulator_debug_probe(struct platform_device *plf) +{ + void __iomem *boot_info_backupram; + int ret, i; + + /* setup dev pointers */ + dev = &plf->dev; + pdev = plf; + + /* save state of registers */ + ret = ab8500_regulator_record_state(AB8500_REGULATOR_STATE_INIT); + if (ret < 0) + dev_err(&plf->dev, "Failed to record init state.\n"); + + /* remove force of external regulators if AB8500 3.0 and DB8500 v2.2 */ + if ((abx500_get_chip_id(&pdev->dev) >= 0x30) && cpu_is_u8500v22()) { + /* + * find ExtSupplyRegu register (bank 0x04, addr 0x08) + * and update value (Vext1 in low-power, Vext2 off). + */ + for (i = 0; i < ARRAY_SIZE(ab8500_force_reg); i++) { + if (ab8500_force_reg[i].bank == 0x04 && + ab8500_force_reg[i].addr == 0x08) { + u8 val, val_mask = 0x0f; + + val = ab8500_force_reg[i].val; + val = (val & ~val_mask) | (0x03 & val_mask); + ab8500_force_reg[i].val = val; + } + } + } + + /* make suspend-force default if board profile is v5x-power */ + boot_info_backupram = ioremap(BOOT_INFO_BACKUPRAM1, 0x4); + + if (boot_info_backupram) { + u8 board_profile; + board_profile = readb( + boot_info_backupram + BOARD_PROFILE_BACKUPRAM1); + dev_dbg(dev, "Board profile is 0x%02x\n", board_profile); + + if (board_profile >= OPTION_BOARD_VERSION_V5X) + setting_suspend_force = true; + + iounmap(boot_info_backupram); + } else { + dev_err(dev, "Failed to read backupram.\n"); + } + + /* create directory */ + ab8500_regulator_dir = debugfs_create_dir("ab8500-regulator", NULL); + if (!ab8500_regulator_dir) + goto exit_no_debugfs; + + /* create "dump" file */ + ab8500_regulator_dump_file = debugfs_create_file("dump", + S_IRUGO, ab8500_regulator_dir, &plf->dev, + &ab8500_regulator_dump_fops); + if (!ab8500_regulator_dump_file) + goto exit_destroy_dir; + + /* create "status" file */ + ab8500_regulator_status_file = debugfs_create_file("status", + S_IRUGO, ab8500_regulator_dir, &plf->dev, + &ab8500_regulator_status_fops); + if (!ab8500_regulator_status_file) + goto exit_destroy_dump_file; + + /* + * create "suspend-force-v5x" file. As indicated by the name, this is + * only applicable for v2_v5x hardware versions. + */ + ab8500_regulator_suspend_force_file = debugfs_create_file( + "suspend-force-v5x", + S_IRUGO, ab8500_regulator_dir, &plf->dev, + &ab8500_regulator_suspend_force_fops); + if (!ab8500_regulator_suspend_force_file) + goto exit_destroy_status_file; + + return 0; + +exit_destroy_status_file: + debugfs_remove(ab8500_regulator_status_file); +exit_destroy_dump_file: + debugfs_remove(ab8500_regulator_dump_file); +exit_destroy_dir: + debugfs_remove(ab8500_regulator_dir); +exit_no_debugfs: + dev_err(&plf->dev, "failed to create debugfs entries.\n"); + return -ENOMEM; +} + +static int __devexit ab8500_regulator_debug_remove(struct platform_device *plf) +{ + debugfs_remove(ab8500_regulator_suspend_force_file); + debugfs_remove(ab8500_regulator_status_file); + debugfs_remove(ab8500_regulator_dump_file); + debugfs_remove(ab8500_regulator_dir); + + return 0; +} + +static struct platform_driver ab8500_regulator_debug_driver = { + .driver = { + .name = "ab8500-regulator-debug", + .owner = THIS_MODULE, + }, + .probe = ab8500_regulator_debug_probe, + .remove = __devexit_p(ab8500_regulator_debug_remove), +}; + +static int __init ab8500_regulator_debug_init(void) +{ + int ret; + + ret = platform_driver_register(&ab8500_regulator_debug_driver); + if (ret) + pr_err("Failed to register ab8500 regulator: %d\n", ret); + + return ret; +} +subsys_initcall(ab8500_regulator_debug_init); + +static void __exit ab8500_regulator_debug_exit(void) +{ + platform_driver_unregister(&ab8500_regulator_debug_driver); +} +module_exit(ab8500_regulator_debug_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bengt Jonsson <bengt.g.jonsson@stericsson.com"); +MODULE_DESCRIPTION("AB8500 Regulator Debug"); +MODULE_ALIAS("platform:ab8500-regulator-debug"); diff --git a/drivers/regulator/ab8500-ext.c b/drivers/regulator/ab8500-ext.c new file mode 100644 index 00000000000..adf8e6b83f2 --- /dev/null +++ b/drivers/regulator/ab8500-ext.c @@ -0,0 +1,292 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * + * Authors: Bengt Jonsson <bengt.g.jonsson@stericsson.com> + * + * This file is based on drivers/regulator/ab8500.c + * + * AB8500 external regulators + * + * ab8500-ext supports the following regulators: + * - VextSupply3 + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/ab8500.h> +#include <linux/regulator/ab8500.h> + +/** + * struct ab8500_ext_regulator_info - ab8500 regulator information + * @dev: device pointer + * @desc: regulator description + * @regulator_dev: regulator device + * @is_enabled: status of regulator (on/off) + * @fixed_uV: typical voltage (for fixed voltage supplies) + * @update_bank: bank to control on/off + * @update_reg: register to control on/off + * @update_mask: mask to enable/disable and set mode of regulator + * @update_val: bits holding the regulator current mode + * @update_val_en: bits to set EN pin active (LPn pin deactive) + * normally this means high power mode + * @update_val_en_lp: bits to set EN pin active and LPn pin active + * normally this means low power mode + * @delay: startup delay in ms + */ +struct ab8500_ext_regulator_info { + struct device *dev; + struct regulator_desc desc; + struct regulator_dev *regulator; + bool is_enabled; + int fixed_uV; + u8 update_bank; + u8 update_reg; + u8 update_mask; + u8 update_val; + u8 update_val_en; + u8 update_val_en_lp; +}; + +static int ab8500_ext_regulator_enable(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = abx500_mask_and_set_register_interruptible(info->dev, + info->update_bank, info->update_reg, + info->update_mask, info->update_val); + if (ret < 0) + dev_err(rdev_get_dev(rdev), + "couldn't set enable bits for regulator\n"); + + info->is_enabled = true; + + dev_dbg(rdev_get_dev(rdev), "%s-enable (bank, reg, mask, value):" + " 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, info->update_val); + + return ret; +} + +static int ab8500_ext_regulator_disable(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = abx500_mask_and_set_register_interruptible(info->dev, + info->update_bank, info->update_reg, + info->update_mask, 0x0); + if (ret < 0) + dev_err(rdev_get_dev(rdev), + "couldn't set disable bits for regulator\n"); + + info->is_enabled = false; + + dev_dbg(rdev_get_dev(rdev), "%s-disable (bank, reg, mask, value):" + " 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, 0x0); + + return ret; +} + +static int ab8500_ext_regulator_is_enabled(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + u8 regval; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = abx500_get_register_interruptible(info->dev, + info->update_bank, info->update_reg, ®val); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't read 0x%x register\n", info->update_reg); + return ret; + } + + dev_dbg(rdev_get_dev(rdev), "%s-is_enabled (bank, reg, mask, value):" + " 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, regval); + + if (regval & info->update_mask) + info->is_enabled = true; + else + info->is_enabled = false; + + return info->is_enabled; +} + +static int ab8500_ext_fixed_get_voltage(struct regulator_dev *rdev) +{ + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + return info->fixed_uV; +} + +static int ab8500_ext_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + /* return the uV for the fixed regulators */ + if (info->fixed_uV) + return info->fixed_uV; + + return -EINVAL; +} + +static struct regulator_ops ab8500_ext_regulator_ops = { + .enable = ab8500_ext_regulator_enable, + .disable = ab8500_ext_regulator_disable, + .is_enabled = ab8500_ext_regulator_is_enabled, + .get_voltage = ab8500_ext_fixed_get_voltage, + .list_voltage = ab8500_ext_list_voltage, +}; + + +static struct ab8500_ext_regulator_info + ab8500_ext_regulator_info[AB8500_NUM_EXT_REGULATORS] = { + [AB8500_EXT_SUPPLY3] = { + .desc = { + .name = "VEXTSUPPLY3", + .ops = &ab8500_ext_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_EXT_SUPPLY3, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 3400000, + .update_bank = 0x04, + .update_reg = 0x08, + .update_mask = 0x30, + .update_val = 0x10, + .update_val_en = 0x10, + .update_val_en_lp = 0x30, + }, +}; + +__devinit int ab8500_ext_regulator_init(struct platform_device *pdev) +{ + struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); + struct ab8500_platform_data *ppdata; + struct ab8500_regulator_platform_data *pdata; + int i, err; + + if (!ab8500) { + dev_err(&pdev->dev, "null mfd parent\n"); + return -EINVAL; + } + ppdata = dev_get_platdata(ab8500->dev); + if (!ppdata) { + dev_err(&pdev->dev, "null parent pdata\n"); + return -EINVAL; + } + + pdata = ppdata->regulator; + if (!pdata) { + dev_err(&pdev->dev, "null pdata\n"); + return -EINVAL; + } + + /* make sure the platform data has the correct size */ + if (pdata->num_ext_regulator != ARRAY_SIZE(ab8500_ext_regulator_info)) { + dev_err(&pdev->dev, "Configuration error: size mismatch.\n"); + return -EINVAL; + } + + /* check for AB8500 2.x */ + if (abx500_get_chip_id(&pdev->dev) < 0x30) { + struct ab8500_ext_regulator_info *info; + + /* VextSupply3LPn is inverted on AB8500 2.x */ + info = &ab8500_ext_regulator_info[AB8500_EXT_SUPPLY3]; + info->update_val = 0x30; + info->update_val_en = 0x30; + info->update_val_en_lp = 0x10; + } + + /* register all regulators */ + for (i = 0; i < ARRAY_SIZE(ab8500_ext_regulator_info); i++) { + struct ab8500_ext_regulator_info *info = NULL; + + /* assign per-regulator data */ + info = &ab8500_ext_regulator_info[i]; + info->dev = &pdev->dev; + + /* register regulator with framework */ + info->regulator = regulator_register(&info->desc, &pdev->dev, + &pdata->ext_regulator[i], info); + if (IS_ERR(info->regulator)) { + err = PTR_ERR(info->regulator); + dev_err(&pdev->dev, "failed to register regulator %s\n", + info->desc.name); + /* when we fail, un-register all earlier regulators */ + while (--i >= 0) { + info = &ab8500_ext_regulator_info[i]; + regulator_unregister(info->regulator); + } + return err; + } + + dev_dbg(rdev_get_dev(info->regulator), + "%s-probed\n", info->desc.name); + } + + return 0; +} + +__devexit int ab8500_ext_regulator_exit(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ab8500_ext_regulator_info); i++) { + struct ab8500_ext_regulator_info *info = NULL; + info = &ab8500_ext_regulator_info[i]; + + dev_vdbg(rdev_get_dev(info->regulator), + "%s-remove\n", info->desc.name); + + regulator_unregister(info->regulator); + } + + return 0; +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bengt Jonsson <bengt.g.jonsson@stericsson.com>"); +MODULE_DESCRIPTION("AB8500 external regulator driver"); +MODULE_ALIAS("platform:ab8500-ext-regulator"); diff --git a/drivers/regulator/ab8500.c b/drivers/regulator/ab8500.c index 02f3c2333c8..0480f55b101 100644 --- a/drivers/regulator/ab8500.c +++ b/drivers/regulator/ab8500.c @@ -20,43 +20,54 @@ #include <linux/regulator/driver.h> #include <linux/regulator/machine.h> #include <linux/regulator/ab8500.h> +#include <linux/mfd/ab8500/gpio.h> /* for sysclkreq pins */ /** * struct ab8500_regulator_info - ab8500 regulator information * @dev: device pointer * @desc: regulator description * @regulator_dev: regulator device + * @is_enabled: status of regulator (on/off) * @max_uV: maximum voltage (for variable voltage supplies) * @min_uV: minimum voltage (for variable voltage supplies) * @fixed_uV: typical voltage (for fixed voltage supplies) + * @load_lp_uA: maximum load in idle (low power) mode * @update_bank: bank to control on/off * @update_reg: register to control on/off - * @update_mask: mask to enable/disable regulator - * @update_val_enable: bits to enable the regulator in normal (high power) mode + * @update_mask: mask to enable/disable and set mode of regulator + * @update_val: bits holding the regulator current mode + * @update_val_idle: bits to enable the regulator in idle (low power) mode + * @update_val_normal: bits to enable the regulator in normal (high power) mode * @voltage_bank: bank to control regulator voltage * @voltage_reg: register to control regulator voltage * @voltage_mask: mask to control regulator voltage * @voltages: supported voltage table * @voltages_len: number of supported voltages for the regulator * @delay: startup/set voltage delay in us + * @gpio_pin: ab8500 gpio pin offset number (for sysclkreq regulator only) */ struct ab8500_regulator_info { struct device *dev; struct regulator_desc desc; struct regulator_dev *regulator; + bool is_enabled; int max_uV; int min_uV; int fixed_uV; + int load_lp_uA; u8 update_bank; u8 update_reg; u8 update_mask; - u8 update_val_enable; + u8 update_val; + u8 update_val_idle; + u8 update_val_normal; u8 voltage_bank; u8 voltage_reg; u8 voltage_mask; int const *voltages; int voltages_len; unsigned int delay; + enum ab8500_pin gpio_pin; }; /* voltage tables for the vauxn/vintcore supplies */ @@ -112,15 +123,17 @@ static int ab8500_regulator_enable(struct regulator_dev *rdev) ret = abx500_mask_and_set_register_interruptible(info->dev, info->update_bank, info->update_reg, - info->update_mask, info->update_val_enable); + info->update_mask, info->update_val); if (ret < 0) dev_err(rdev_get_dev(rdev), "couldn't set enable bits for regulator\n"); + info->is_enabled = true; + dev_vdbg(rdev_get_dev(rdev), "%s-enable (bank, reg, mask, value): 0x%x, 0x%x, 0x%x, 0x%x\n", info->desc.name, info->update_bank, info->update_reg, - info->update_mask, info->update_val_enable); + info->update_mask, info->update_val); return ret; } @@ -142,6 +155,8 @@ static int ab8500_regulator_disable(struct regulator_dev *rdev) dev_err(rdev_get_dev(rdev), "couldn't set disable bits for regulator\n"); + info->is_enabled = false; + dev_vdbg(rdev_get_dev(rdev), "%s-disable (bank, reg, mask, value): 0x%x, 0x%x, 0x%x, 0x%x\n", info->desc.name, info->update_bank, info->update_reg, @@ -150,6 +165,88 @@ static int ab8500_regulator_disable(struct regulator_dev *rdev) return ret; } +static unsigned int ab8500_regulator_get_optimum_mode( + struct regulator_dev *rdev, int input_uV, + int output_uV, int load_uA) +{ + unsigned int mode; + + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + if (load_uA <= info->load_lp_uA) + mode = REGULATOR_MODE_IDLE; + else + mode = REGULATOR_MODE_NORMAL; + + return mode; +} + +static int ab8500_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + int ret = 0; + + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + switch (mode) { + case REGULATOR_MODE_NORMAL: + info->update_val = info->update_val_normal; + break; + case REGULATOR_MODE_IDLE: + info->update_val = info->update_val_idle; + break; + default: + return -EINVAL; + } + + if (info->is_enabled) { + ret = abx500_mask_and_set_register_interruptible(info->dev, + info->update_bank, info->update_reg, + info->update_mask, info->update_val); + if (ret < 0) + dev_err(rdev_get_dev(rdev), + "couldn't set regulator mode\n"); + + dev_vdbg(rdev_get_dev(rdev), + "%s-set_mode (bank, reg, mask, value): " + "0x%x, 0x%x, 0x%x, 0x%x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, info->update_val); + } + + return ret; +} + +static unsigned int ab8500_regulator_get_mode(struct regulator_dev *rdev) +{ + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + if (info->update_val == info->update_val_normal) + ret = REGULATOR_MODE_NORMAL; + else if (info->update_val == info->update_val_idle) + ret = REGULATOR_MODE_IDLE; + else + ret = -EINVAL; + + return ret; +} + static int ab8500_regulator_is_enabled(struct regulator_dev *rdev) { int ret; @@ -176,9 +273,11 @@ static int ab8500_regulator_is_enabled(struct regulator_dev *rdev) info->update_mask, regval); if (regval & info->update_mask) - return true; + info->is_enabled = true; else - return false; + info->is_enabled = false; + + return info->is_enabled; } static int ab8500_list_voltage(struct regulator_dev *rdev, unsigned selector) @@ -274,8 +373,13 @@ static int ab8500_regulator_set_voltage(struct regulator_dev *rdev, *selector = ret; + /* vintcore register has a different layout */ + if (info->desc.id == AB8500_LDO_INTCORE) + regval = ((u8)ret) << 3; + else + regval = (u8)ret; + /* set the registers for the request */ - regval = (u8)ret; ret = abx500_mask_and_set_register_interruptible(info->dev, info->voltage_bank, info->voltage_reg, info->voltage_mask, regval); @@ -315,9 +419,12 @@ static int ab8500_regulator_set_voltage_time_sel(struct regulator_dev *rdev, return info->delay; } -static struct regulator_ops ab8500_regulator_ops = { +static struct regulator_ops ab8500_regulator_volt_mode_ops = { .enable = ab8500_regulator_enable, .disable = ab8500_regulator_disable, + .get_optimum_mode = ab8500_regulator_get_optimum_mode, + .set_mode = ab8500_regulator_set_mode, + .get_mode = ab8500_regulator_get_mode, .is_enabled = ab8500_regulator_is_enabled, .get_voltage = ab8500_regulator_get_voltage, .set_voltage = ab8500_regulator_set_voltage, @@ -338,16 +445,115 @@ static int ab8500_fixed_get_voltage(struct regulator_dev *rdev) return info->fixed_uV; } -static struct regulator_ops ab8500_regulator_fixed_ops = { +static struct regulator_ops ab8500_regulator_mode_ops = { .enable = ab8500_regulator_enable, .disable = ab8500_regulator_disable, .is_enabled = ab8500_regulator_is_enabled, + .get_optimum_mode = ab8500_regulator_get_optimum_mode, + .set_mode = ab8500_regulator_set_mode, + .get_mode = ab8500_regulator_get_mode, .get_voltage = ab8500_fixed_get_voltage, .list_voltage = ab8500_list_voltage, .enable_time = ab8500_regulator_enable_time, .set_voltage_time_sel = ab8500_regulator_set_voltage_time_sel, }; +static struct regulator_ops ab8500_regulator_ops = { + .enable = ab8500_regulator_enable, + .disable = ab8500_regulator_disable, + .is_enabled = ab8500_regulator_is_enabled, + .get_voltage = ab8500_fixed_get_voltage, + .list_voltage = ab8500_list_voltage, +}; + +static int ab8500_sysclkreq_enable(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = ab8500_gpio_config_select(info->dev, info->gpio_pin, false); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't set sysclkreq pin selection\n"); + return ret; + } + + info->is_enabled = true; + + dev_vdbg(rdev_get_dev(rdev), + "%s-enable (gpio_pin, gpio_select): %i, false\n", + info->desc.name, info->gpio_pin); + + return ret; +} + +static int ab8500_sysclkreq_disable(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = ab8500_gpio_config_select(info->dev, info->gpio_pin, true); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't set gpio pin selection\n"); + return ret; + } + + info->is_enabled = false; + + dev_vdbg(rdev_get_dev(rdev), + "%s-disable (gpio_pin, gpio_select): %i, true\n", + info->desc.name, info->gpio_pin); + + return ret; +} + +static int ab8500_sysclkreq_is_enabled(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + bool gpio_select; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = ab8500_gpio_config_get_select(info->dev, info->gpio_pin, + &gpio_select); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't read gpio pin selection\n"); + return ret; + } + + info->is_enabled = !gpio_select; + + dev_vdbg(rdev_get_dev(rdev), + "%s-is_enabled (gpio_pin, is_enabled): %i, %i\n", + info->desc.name, info->gpio_pin, !gpio_select); + + return info->is_enabled; +} + +static struct regulator_ops ab8500_sysclkreq_ops = { + .enable = ab8500_sysclkreq_enable, + .disable = ab8500_sysclkreq_disable, + .is_enabled = ab8500_sysclkreq_is_enabled, + .get_voltage = ab8500_fixed_get_voltage, + .list_voltage = ab8500_list_voltage, +}; + static struct ab8500_regulator_info ab8500_regulator_info[AB8500_NUM_REGULATORS] = { /* @@ -359,7 +565,7 @@ static struct ab8500_regulator_info [AB8500_LDO_AUX1] = { .desc = { .name = "LDO-AUX1", - .ops = &ab8500_regulator_ops, + .ops = &ab8500_regulator_volt_mode_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_AUX1, .owner = THIS_MODULE, @@ -367,10 +573,13 @@ static struct ab8500_regulator_info }, .min_uV = 1100000, .max_uV = 3300000, + .load_lp_uA = 5000, .update_bank = 0x04, .update_reg = 0x09, .update_mask = 0x03, - .update_val_enable = 0x01, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, .voltage_bank = 0x04, .voltage_reg = 0x1f, .voltage_mask = 0x0f, @@ -380,7 +589,7 @@ static struct ab8500_regulator_info [AB8500_LDO_AUX2] = { .desc = { .name = "LDO-AUX2", - .ops = &ab8500_regulator_ops, + .ops = &ab8500_regulator_volt_mode_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_AUX2, .owner = THIS_MODULE, @@ -388,10 +597,13 @@ static struct ab8500_regulator_info }, .min_uV = 1100000, .max_uV = 3300000, + .load_lp_uA = 5000, .update_bank = 0x04, .update_reg = 0x09, .update_mask = 0x0c, - .update_val_enable = 0x04, + .update_val = 0x04, + .update_val_idle = 0x0c, + .update_val_normal = 0x04, .voltage_bank = 0x04, .voltage_reg = 0x20, .voltage_mask = 0x0f, @@ -401,7 +613,7 @@ static struct ab8500_regulator_info [AB8500_LDO_AUX3] = { .desc = { .name = "LDO-AUX3", - .ops = &ab8500_regulator_ops, + .ops = &ab8500_regulator_volt_mode_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_AUX3, .owner = THIS_MODULE, @@ -409,10 +621,13 @@ static struct ab8500_regulator_info }, .min_uV = 1100000, .max_uV = 3300000, + .load_lp_uA = 5000, .update_bank = 0x04, .update_reg = 0x0a, .update_mask = 0x03, - .update_val_enable = 0x01, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, .voltage_bank = 0x04, .voltage_reg = 0x21, .voltage_mask = 0x07, @@ -422,7 +637,7 @@ static struct ab8500_regulator_info [AB8500_LDO_INTCORE] = { .desc = { .name = "LDO-INTCORE", - .ops = &ab8500_regulator_ops, + .ops = &ab8500_regulator_volt_mode_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_INTCORE, .owner = THIS_MODULE, @@ -430,10 +645,13 @@ static struct ab8500_regulator_info }, .min_uV = 1100000, .max_uV = 3300000, + .load_lp_uA = 5000, .update_bank = 0x03, .update_reg = 0x80, .update_mask = 0x44, - .update_val_enable = 0x04, + .update_val = 0x44, + .update_val_idle = 0x44, + .update_val_normal = 0x04, .voltage_bank = 0x03, .voltage_reg = 0x80, .voltage_mask = 0x38, @@ -449,7 +667,7 @@ static struct ab8500_regulator_info [AB8500_LDO_TVOUT] = { .desc = { .name = "LDO-TVOUT", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_mode_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_TVOUT, .owner = THIS_MODULE, @@ -457,15 +675,18 @@ static struct ab8500_regulator_info }, .delay = 10000, .fixed_uV = 2000000, + .load_lp_uA = 1000, .update_bank = 0x03, .update_reg = 0x80, .update_mask = 0x82, - .update_val_enable = 0x02, + .update_val = 0x02, + .update_val_idle = 0x82, + .update_val_normal = 0x02, }, [AB8500_LDO_USB] = { .desc = { .name = "LDO-USB", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_USB, .owner = THIS_MODULE, @@ -475,12 +696,14 @@ static struct ab8500_regulator_info .update_bank = 0x03, .update_reg = 0x82, .update_mask = 0x03, - .update_val_enable = 0x01, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, }, [AB8500_LDO_AUDIO] = { .desc = { .name = "LDO-AUDIO", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_AUDIO, .owner = THIS_MODULE, @@ -490,12 +713,12 @@ static struct ab8500_regulator_info .update_bank = 0x03, .update_reg = 0x83, .update_mask = 0x02, - .update_val_enable = 0x02, + .update_val = 0x02, }, [AB8500_LDO_ANAMIC1] = { .desc = { .name = "LDO-ANAMIC1", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_ANAMIC1, .owner = THIS_MODULE, @@ -505,12 +728,12 @@ static struct ab8500_regulator_info .update_bank = 0x03, .update_reg = 0x83, .update_mask = 0x08, - .update_val_enable = 0x08, + .update_val = 0x08, }, [AB8500_LDO_ANAMIC2] = { .desc = { .name = "LDO-ANAMIC2", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_ANAMIC2, .owner = THIS_MODULE, @@ -520,12 +743,12 @@ static struct ab8500_regulator_info .update_bank = 0x03, .update_reg = 0x83, .update_mask = 0x10, - .update_val_enable = 0x10, + .update_val = 0x10, }, [AB8500_LDO_DMIC] = { .desc = { .name = "LDO-DMIC", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_DMIC, .owner = THIS_MODULE, @@ -535,25 +758,58 @@ static struct ab8500_regulator_info .update_bank = 0x03, .update_reg = 0x83, .update_mask = 0x04, - .update_val_enable = 0x04, + .update_val = 0x04, }, + + /* + * Regulators with fixed voltage and normal/idle modes + */ [AB8500_LDO_ANA] = { .desc = { .name = "LDO-ANA", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_mode_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_ANA, .owner = THIS_MODULE, .n_voltages = 1, }, .fixed_uV = 1200000, + .load_lp_uA = 1000, .update_bank = 0x04, .update_reg = 0x06, .update_mask = 0x0c, - .update_val_enable = 0x04, + .update_val = 0x04, + .update_val_idle = 0x0c, + .update_val_normal = 0x04, }, - + /* + * SysClkReq regulators + */ + [AB8500_SYSCLKREQ_2] = { + .desc = { + .name = "SYSCLKREQ-2", + .ops = &ab8500_sysclkreq_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_SYSCLKREQ_2, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 1, /* bogus value */ + .gpio_pin = AB8500_PIN_GPIO1, + }, + [AB8500_SYSCLKREQ_4] = { + .desc = { + .name = "SYSCLKREQ-4", + .ops = &ab8500_sysclkreq_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_SYSCLKREQ_4, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 1, /* bogus value */ + .gpio_pin = AB8500_PIN_GPIO3, + }, }; struct ab8500_reg_init { @@ -571,11 +827,19 @@ struct ab8500_reg_init { static struct ab8500_reg_init ab8500_reg_init[] = { /* + * 0x03, VarmRequestCtrl + * 0x0c, VapeRequestCtrl + * 0x30, Vsmps1RequestCtrl + * 0xc0, Vsmps2RequestCtrl + */ + REG_INIT(AB8500_REGUREQUESTCTRL1, 0x03, 0x03, 0xff), + /* + * 0x03, Vsmps3RequestCtrl + * 0x0c, VpllRequestCtrl * 0x30, VanaRequestCtrl - * 0x0C, VpllRequestCtrl * 0xc0, VextSupply1RequestCtrl */ - REG_INIT(AB8500_REGUREQUESTCTRL2, 0x03, 0x04, 0xfc), + REG_INIT(AB8500_REGUREQUESTCTRL2, 0x03, 0x04, 0xff), /* * 0x03, VextSupply2RequestCtrl * 0x0c, VextSupply3RequestCtrl @@ -589,57 +853,82 @@ static struct ab8500_reg_init ab8500_reg_init[] = { */ REG_INIT(AB8500_REGUREQUESTCTRL4, 0x03, 0x06, 0x07), /* + * 0x01, Vsmps1SysClkReq1HPValid + * 0x02, Vsmps2SysClkReq1HPValid + * 0x04, Vsmps3SysClkReq1HPValid * 0x08, VanaSysClkReq1HPValid + * 0x10, VpllSysClkReq1HPValid * 0x20, Vaux1SysClkReq1HPValid * 0x40, Vaux2SysClkReq1HPValid * 0x80, Vaux3SysClkReq1HPValid */ - REG_INIT(AB8500_REGUSYSCLKREQ1HPVALID1, 0x03, 0x07, 0xe8), + REG_INIT(AB8500_REGUSYSCLKREQ1HPVALID1, 0x03, 0x07, 0xff), /* + * 0x01, VapeSysClkReq1HPValid + * 0x02, VarmSysClkReq1HPValid + * 0x04, VbbSysClkReq1HPValid + * 0x08, VmodSysClkReq1HPValid * 0x10, VextSupply1SysClkReq1HPValid * 0x20, VextSupply2SysClkReq1HPValid * 0x40, VextSupply3SysClkReq1HPValid */ - REG_INIT(AB8500_REGUSYSCLKREQ1HPVALID2, 0x03, 0x08, 0x70), + REG_INIT(AB8500_REGUSYSCLKREQ1HPVALID2, 0x03, 0x08, 0x7f), /* + * 0x01, Vsmps1HwHPReq1Valid + * 0x02, Vsmps2HwHPReq1Valid + * 0x04, Vsmps3HwHPReq1Valid * 0x08, VanaHwHPReq1Valid + * 0x10, VpllHwHPReq1Valid * 0x20, Vaux1HwHPReq1Valid * 0x40, Vaux2HwHPReq1Valid * 0x80, Vaux3HwHPReq1Valid */ - REG_INIT(AB8500_REGUHWHPREQ1VALID1, 0x03, 0x09, 0xe8), + REG_INIT(AB8500_REGUHWHPREQ1VALID1, 0x03, 0x09, 0xff), /* * 0x01, VextSupply1HwHPReq1Valid * 0x02, VextSupply2HwHPReq1Valid * 0x04, VextSupply3HwHPReq1Valid + * 0x08, VmodHwHPReq1Valid */ - REG_INIT(AB8500_REGUHWHPREQ1VALID2, 0x03, 0x0a, 0x07), + REG_INIT(AB8500_REGUHWHPREQ1VALID2, 0x03, 0x0a, 0x0f), /* + * 0x01, Vsmps1HwHPReq2Valid + * 0x02, Vsmps2HwHPReq2Valid + * 0x03, Vsmps3HwHPReq2Valid * 0x08, VanaHwHPReq2Valid + * 0x10, VpllHwHPReq2Valid * 0x20, Vaux1HwHPReq2Valid * 0x40, Vaux2HwHPReq2Valid * 0x80, Vaux3HwHPReq2Valid */ - REG_INIT(AB8500_REGUHWHPREQ2VALID1, 0x03, 0x0b, 0xe8), + REG_INIT(AB8500_REGUHWHPREQ2VALID1, 0x03, 0x0b, 0xff), /* * 0x01, VextSupply1HwHPReq2Valid * 0x02, VextSupply2HwHPReq2Valid * 0x04, VextSupply3HwHPReq2Valid + * 0x08, VmodHwHPReq2Valid */ - REG_INIT(AB8500_REGUHWHPREQ2VALID2, 0x03, 0x0c, 0x07), + REG_INIT(AB8500_REGUHWHPREQ2VALID2, 0x03, 0x0c, 0x0f), /* + * 0x01, VapeSwHPReqValid + * 0x02, VarmSwHPReqValid + * 0x04, Vsmps1SwHPReqValid + * 0x08, Vsmps2SwHPReqValid + * 0x10, Vsmps3SwHPReqValid * 0x20, VanaSwHPReqValid + * 0x40, VpllSwHPReqValid * 0x80, Vaux1SwHPReqValid */ - REG_INIT(AB8500_REGUSWHPREQVALID1, 0x03, 0x0d, 0xa0), + REG_INIT(AB8500_REGUSWHPREQVALID1, 0x03, 0x0d, 0xff), /* * 0x01, Vaux2SwHPReqValid * 0x02, Vaux3SwHPReqValid * 0x04, VextSupply1SwHPReqValid * 0x08, VextSupply2SwHPReqValid * 0x10, VextSupply3SwHPReqValid + * 0x20, VmodSwHPReqValid */ - REG_INIT(AB8500_REGUSWHPREQVALID2, 0x03, 0x0e, 0x1f), + REG_INIT(AB8500_REGUSWHPREQVALID2, 0x03, 0x0e, 0x3f), /* * 0x02, SysClkReq2Valid1 * ... @@ -673,8 +962,28 @@ static struct ab8500_reg_init ab8500_reg_init[] = { */ REG_INIT(AB8500_REGUCTRL1VAMIC, 0x03, 0x84, 0x03), /* - * 0x0c, VanaRegu + * 0x03, Vsmps1Regu + * 0x0c, Vsmps1SelCtrl + * 0x10, Vsmps1AutoMode + * 0x20, Vsmps1PWMMode + */ + REG_INIT(AB8500_VSMPS1REGU, 0x04, 0x03, 0x3f), + /* + * 0x03, Vsmps2Regu + * 0x0c, Vsmps2SelCtrl + * 0x10, Vsmps2AutoMode + * 0x20, Vsmps2PWMMode + */ + REG_INIT(AB8500_VSMPS2REGU, 0x04, 0x04, 0x3f), + /* + * 0x03, Vsmps3Regu + * 0x0c, Vsmps3SelCtrl + * NOTE! PRCMU register + */ + REG_INIT(AB8500_VSMPS3REGU, 0x04, 0x05, 0x0f), + /* * 0x03, VpllRegu + * 0x0c, VanaRegu */ REG_INIT(AB8500_VPLLVANAREGU, 0x04, 0x06, 0x0f), /* @@ -696,14 +1005,45 @@ static struct ab8500_reg_init ab8500_reg_init[] = { */ REG_INIT(AB8500_VAUX12REGU, 0x04, 0x09, 0x0f), /* + * 0x0c, Vrf1Regu * 0x03, Vaux3Regu */ - REG_INIT(AB8500_VRF1VAUX3REGU, 0x04, 0x0a, 0x03), + REG_INIT(AB8500_VRF1VAUX3REGU, 0x04, 0x0a, 0x0f), /* * 0x3f, Vsmps1Sel1 */ REG_INIT(AB8500_VSMPS1SEL1, 0x04, 0x13, 0x3f), /* + * 0x3f, Vsmps1Sel2 + */ + REG_INIT(AB8500_VSMPS1SEL2, 0x04, 0x14, 0x3f), + /* + * 0x3f, Vsmps1Sel3 + */ + REG_INIT(AB8500_VSMPS1SEL3, 0x04, 0x15, 0x3f), + /* + * 0x3f, Vsmps2Sel1 + */ + REG_INIT(AB8500_VSMPS2SEL1, 0x04, 0x17, 0x3f), + /* + * 0x3f, Vsmps2Sel2 + */ + REG_INIT(AB8500_VSMPS2SEL2, 0x04, 0x18, 0x3f), + /* + * 0x3f, Vsmps2Sel3 + */ + REG_INIT(AB8500_VSMPS2SEL3, 0x04, 0x19, 0x3f), + /* + * 0x7f, Vsmps3Sel1 + * NOTE! PRCMU register + */ + REG_INIT(AB8500_VSMPS3SEL1, 0x04, 0x1b, 0x7f), + /* + * 0x7f, Vsmps3Sel2 + * NOTE! PRCMU register + */ + REG_INIT(AB8500_VSMPS3SEL2, 0x04, 0x1c, 0x7f), + /* * 0x0f, Vaux1Sel */ REG_INIT(AB8500_VAUX1SEL, 0x04, 0x1f, 0x0f), @@ -713,13 +1053,16 @@ static struct ab8500_reg_init ab8500_reg_init[] = { REG_INIT(AB8500_VAUX2SEL, 0x04, 0x20, 0x0f), /* * 0x07, Vaux3Sel + * 0x30, Vrf1Sel */ - REG_INIT(AB8500_VRF1VAUX3SEL, 0x04, 0x21, 0x07), + REG_INIT(AB8500_VRF1VAUX3SEL, 0x04, 0x21, 0x37), /* * 0x01, VextSupply12LP */ REG_INIT(AB8500_REGUCTRL2SPARE, 0x04, 0x22, 0x01), /* + * 0x01, VpllDisch + * 0x02, Vrf1Disch * 0x04, Vaux1Disch * 0x08, Vaux2Disch * 0x10, Vaux3Disch @@ -727,26 +1070,36 @@ static struct ab8500_reg_init ab8500_reg_init[] = { * 0x40, VTVoutDisch * 0x80, VaudioDisch */ - REG_INIT(AB8500_REGUCTRLDISCH, 0x04, 0x43, 0xfc), + REG_INIT(AB8500_REGUCTRLDISCH, 0x04, 0x43, 0xff), /* + * 0x01, VsimDisch * 0x02, VanaDisch * 0x04, VdmicPullDownEna + * 0x08, VpllPullDownEna * 0x10, VdmicDisch */ - REG_INIT(AB8500_REGUCTRLDISCH2, 0x04, 0x44, 0x16), + REG_INIT(AB8500_REGUCTRLDISCH2, 0x04, 0x44, 0x1f), }; static __devinit int ab8500_regulator_probe(struct platform_device *pdev) { struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); - struct ab8500_platform_data *pdata; + struct ab8500_platform_data *ppdata; + struct ab8500_regulator_platform_data *pdata; int i, err; if (!ab8500) { dev_err(&pdev->dev, "null mfd parent\n"); return -EINVAL; } - pdata = dev_get_platdata(ab8500->dev); + + ppdata = dev_get_platdata(ab8500->dev); + if (!ppdata) { + dev_err(&pdev->dev, "null parent pdata\n"); + return -EINVAL; + } + + pdata = ppdata->regulator; if (!pdata) { dev_err(&pdev->dev, "null pdata\n"); return -EINVAL; @@ -759,31 +1112,24 @@ static __devinit int ab8500_regulator_probe(struct platform_device *pdev) } /* initialize registers */ - for (i = 0; i < pdata->num_regulator_reg_init; i++) { + for (i = 0; i < pdata->num_reg_init; i++) { int id; - u8 value; + u8 mask, value; - id = pdata->regulator_reg_init[i].id; - value = pdata->regulator_reg_init[i].value; + id = pdata->reg_init[i].id; + mask = pdata->reg_init[i].mask; + value = pdata->reg_init[i].value; /* check for configuration errors */ - if (id >= AB8500_NUM_REGULATOR_REGISTERS) { - dev_err(&pdev->dev, - "Configuration error: id outside range.\n"); - return -EINVAL; - } - if (value & ~ab8500_reg_init[id].mask) { - dev_err(&pdev->dev, - "Configuration error: value outside mask.\n"); - return -EINVAL; - } + BUG_ON(id >= AB8500_NUM_REGULATOR_REGISTERS); + BUG_ON(value & ~mask); + BUG_ON(mask & ~ab8500_reg_init[id].mask); /* initialize register */ err = abx500_mask_and_set_register_interruptible(&pdev->dev, ab8500_reg_init[id].bank, ab8500_reg_init[id].addr, - ab8500_reg_init[id].mask, - value); + mask, value); if (err < 0) { dev_err(&pdev->dev, "Failed to initialize 0x%02x, 0x%02x.\n", @@ -795,10 +1141,32 @@ static __devinit int ab8500_regulator_probe(struct platform_device *pdev) " init: 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", ab8500_reg_init[id].bank, ab8500_reg_init[id].addr, - ab8500_reg_init[id].mask, - value); + mask, value); } + /* + * This changes the default setting for VextSupply3Regu to low power. + * Active high or low is depending on OTP which is changed from ab8500v3.0. + * Remove this when ab8500v2.0 is no longer important. + * This only affects power consumption and it depends on the + * HREF OTP configurations. + */ + if (abx500_get_chip_id(&pdev->dev) < 0x30) { + err = abx500_mask_and_set_register_interruptible(&pdev->dev, + AB8500_REGU_CTRL2, 0x08, 0x30, 0x30); + if (err < 0) { + dev_err(&pdev->dev, + "Failed to override 0x%02x, 0x%02x.\n", + AB8500_REGU_CTRL2, 0x08); + return err; + } + } + + /* register external regulators (before Vaux1, 2 and 3) */ + err = ab8500_ext_regulator_init(pdev); + if (err) + return err; + /* register all regulators */ for (i = 0; i < ARRAY_SIZE(ab8500_regulator_info); i++) { struct ab8500_regulator_info *info = NULL; @@ -843,7 +1211,7 @@ static __devinit int ab8500_regulator_probe(struct platform_device *pdev) static __devexit int ab8500_regulator_remove(struct platform_device *pdev) { - int i; + int i, err; for (i = 0; i < ARRAY_SIZE(ab8500_regulator_info); i++) { struct ab8500_regulator_info *info = NULL; @@ -855,6 +1223,11 @@ static __devexit int ab8500_regulator_remove(struct platform_device *pdev) regulator_unregister(info->regulator); } + /* remove external regulators (after Vaux1, 2 and 3) */ + err = ab8500_ext_regulator_exit(pdev); + if (err) + return err; + return 0; } @@ -887,5 +1260,6 @@ module_exit(ab8500_regulator_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Sundar Iyer <sundar.iyer@stericsson.com>"); +MODULE_AUTHOR("Bengt Jonsson <bengt.g.jonsson@stericsson.com>"); MODULE_DESCRIPTION("Regulator Driver for ST-Ericsson AB8500 Mixed-Sig PMIC"); MODULE_ALIAS("platform:ab8500-regulator"); diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index d8e6a429e8b..504d5dc4777 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -84,6 +84,7 @@ struct regulator { #ifdef CONFIG_DEBUG_FS struct dentry *debugfs; #endif + int use; }; static int _regulator_is_enabled(struct regulator_dev *rdev); @@ -175,11 +176,13 @@ static int regulator_check_consumers(struct regulator_dev *rdev, */ if (!regulator->min_uV && !regulator->max_uV) continue; - - if (*max_uV > regulator->max_uV) - *max_uV = regulator->max_uV; - if (*min_uV < regulator->min_uV) - *min_uV = regulator->min_uV; + + if (regulator->use) { + if (*max_uV > regulator->max_uV) + *max_uV = regulator->max_uV; + if (*min_uV < regulator->min_uV) + *min_uV = regulator->min_uV; + } } if (*min_uV > *max_uV) @@ -578,6 +581,32 @@ static ssize_t regulator_suspend_standby_state_show(struct device *dev, static DEVICE_ATTR(suspend_standby_state, 0444, regulator_suspend_standby_state_show, NULL); +static ssize_t regulator_use_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + struct regulator *reg; + size_t size = 0; + + if (rdev->use_count == 0) + return sprintf(buf, "no users\n"); + + list_for_each_entry(reg, &rdev->consumer_list, list) { + if (!reg->use) + continue; + + if (reg->dev != NULL) + size += sprintf((buf + size), "%s (%d) ", + dev_name(reg->dev), reg->use); + else + size += sprintf((buf + size), "unknown (%d) ", + reg->use); + } + size += sprintf((buf + size), "\n"); + + return size; +} +static DEVICE_ATTR(use, 0444, regulator_use_show, NULL); /* * These are the only attributes are present for all regulators. @@ -1427,6 +1456,8 @@ int regulator_enable(struct regulator *regulator) if (ret != 0) regulator_disable(rdev->supply); + else + regulator->use++; return ret; } @@ -1500,6 +1531,9 @@ int regulator_disable(struct regulator *regulator) if (ret == 0 && rdev->supply) regulator_disable(rdev->supply); + if (ret == 0) + regulator->use--; + return ret; } EXPORT_SYMBOL_GPL(regulator_disable); @@ -2439,6 +2473,10 @@ static int add_regulator_attributes(struct regulator_dev *rdev) struct regulator_ops *ops = rdev->desc->ops; int status = 0; + status = device_create_file(dev, &dev_attr_use); + if (status < 0) + dev_warn(dev, "Create sysfs file \"use\" failed"); + /* some attributes need specific methods to be displayed */ if (ops->get_voltage || ops->get_voltage_sel) { status = device_create_file(dev, &dev_attr_microvolts); diff --git a/drivers/regulator/db5500-prcmu.c b/drivers/regulator/db5500-prcmu.c new file mode 100644 index 00000000000..bf2aeeee399 --- /dev/null +++ b/drivers/regulator/db5500-prcmu.c @@ -0,0 +1,330 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Authors: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson + * Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson + * + * Power domain regulators on DB5500 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/db5500-prcmu.h> + +#include <linux/mfd/dbx500-prcmu.h> + +#include "dbx500-prcmu.h" +static int db5500_regulator_enable(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-%s-enable\n", + info->desc.name); + + info->is_enabled = true; + if (!info->exclude_from_power_state) + power_state_active_enable(); + + return 0; +} + +static int db5500_regulator_disable(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + int ret = 0; + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-%s-disable\n", + info->desc.name); + + info->is_enabled = false; + if (!info->exclude_from_power_state) + ret = power_state_active_disable(); + + return ret; +} + +static int db5500_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-%s-is_enabled (is_enabled):" + " %i\n", info->desc.name, info->is_enabled); + + return info->is_enabled; +} + +/* db5500 regulator operations */ +static struct regulator_ops db5500_regulator_ops = { + .enable = db5500_regulator_enable, + .disable = db5500_regulator_disable, + .is_enabled = db5500_regulator_is_enabled, +}; + +/* + * EPOD control + */ +static bool epod_on[NUM_EPOD_ID]; +static bool epod_ramret[NUM_EPOD_ID]; + +static inline int epod_id_to_index(u16 epod_id) +{ + return epod_id - DB5500_EPOD_ID_BASE; +} + +static int enable_epod(u16 epod_id, bool ramret) +{ + int idx = epod_id_to_index(epod_id); + int ret; + + if (ramret) { + if (!epod_on[idx]) { + ret = prcmu_set_epod(epod_id, EPOD_STATE_RAMRET); + if (ret < 0) + return ret; + } + epod_ramret[idx] = true; + } else { + ret = prcmu_set_epod(epod_id, EPOD_STATE_ON); + if (ret < 0) + return ret; + epod_on[idx] = true; + } + + return 0; +} + +static int disable_epod(u16 epod_id, bool ramret) +{ + int idx = epod_id_to_index(epod_id); + int ret; + + if (ramret) { + if (!epod_on[idx]) { + ret = prcmu_set_epod(epod_id, EPOD_STATE_OFF); + if (ret < 0) + return ret; + } + epod_ramret[idx] = false; + } else { + if (epod_ramret[idx]) { + ret = prcmu_set_epod(epod_id, EPOD_STATE_RAMRET); + if (ret < 0) + return ret; + } else { + ret = prcmu_set_epod(epod_id, EPOD_STATE_OFF); + if (ret < 0) + return ret; + } + epod_on[idx] = false; + } + + return 0; +} + +/* + * Regulator switch + */ +static int db5500_regulator_switch_enable(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-switch-%s-enable\n", + info->desc.name); + + ret = enable_epod(info->epod_id, info->is_ramret); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "regulator-switch-%s-enable: prcmu call failed\n", + info->desc.name); + goto out; + } + + info->is_enabled = true; +out: + return ret; +} + +static int db5500_regulator_switch_disable(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-switch-%s-disable\n", + info->desc.name); + + ret = disable_epod(info->epod_id, info->is_ramret); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "regulator_switch-%s-disable: prcmu call failed\n", + info->desc.name); + goto out; + } + + info->is_enabled = 0; +out: + return ret; +} + +static int db5500_regulator_switch_is_enabled(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), + "regulator-switch-%s-is_enabled (is_enabled): %i\n", + info->desc.name, info->is_enabled); + + return info->is_enabled; +} + +static struct regulator_ops db5500_regulator_switch_ops = { + .enable = db5500_regulator_switch_enable, + .disable = db5500_regulator_switch_disable, + .is_enabled = db5500_regulator_switch_is_enabled, +}; + +/* + * Regulator information + */ +#define DB5500_REGULATOR_SWITCH(_name, reg) \ + [DB5500_REGULATOR_SWITCH_##reg] = { \ + .desc = { \ + .name = _name, \ + .id = DB5500_REGULATOR_SWITCH_##reg, \ + .ops = &db5500_regulator_switch_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + .epod_id = DB5500_EPOD_ID_##reg, \ +} + +static struct dbx500_regulator_info + dbx500_regulator_info[DB5500_NUM_REGULATORS] = { + [DB5500_REGULATOR_VAPE] = { + .desc = { + .name = "db5500-vape", + .id = DB5500_REGULATOR_VAPE, + .ops = &db5500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + DB5500_REGULATOR_SWITCH("db5500-sga", SGA), + DB5500_REGULATOR_SWITCH("db5500-hva", HVA), + DB5500_REGULATOR_SWITCH("db5500-sia", SIA), + DB5500_REGULATOR_SWITCH("db5500-disp", DISP), + DB5500_REGULATOR_SWITCH("db5500-esram12", ESRAM12), +}; + +static int __devinit db5500_regulator_probe(struct platform_device *pdev) +{ + struct regulator_init_data *db5500_init_data = + dev_get_platdata(&pdev->dev); + int i, err; + + /* register all regulators */ + for (i = 0; i < ARRAY_SIZE(dbx500_regulator_info); i++) { + struct dbx500_regulator_info *info; + struct regulator_init_data *init_data = &db5500_init_data[i]; + + /* assign per-regulator data */ + info = &dbx500_regulator_info[i]; + info->dev = &pdev->dev; + + /* register with the regulator framework */ + info->rdev = regulator_register(&info->desc, &pdev->dev, + init_data, info); + if (IS_ERR(info->rdev)) { + err = PTR_ERR(info->rdev); + dev_err(&pdev->dev, "failed to register %s: err %i\n", + info->desc.name, err); + + /* if failing, unregister all earlier regulators */ + i--; + while (i >= 0) { + info = &dbx500_regulator_info[i]; + regulator_unregister(info->rdev); + i--; + } + return err; + } + + dev_dbg(rdev_get_dev(info->rdev), + "regulator-%s-probed\n", info->desc.name); + } + + return 0; +} + +static int __exit db5500_regulator_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dbx500_regulator_info); i++) { + struct dbx500_regulator_info *info; + info = &dbx500_regulator_info[i]; + + dev_vdbg(rdev_get_dev(info->rdev), + "regulator-%s-remove\n", info->desc.name); + + regulator_unregister(info->rdev); + } + + return 0; +} + +static struct platform_driver db5500_regulator_driver = { + .driver = { + .name = "db5500-prcmu-regulators", + .owner = THIS_MODULE, + }, + .probe = db5500_regulator_probe, + .remove = __exit_p(db5500_regulator_remove), +}; + +static int __init db5500_regulator_init(void) +{ + int ret; + + ret = platform_driver_register(&db5500_regulator_driver); + if (ret < 0) + return -ENODEV; + + return 0; +} + +static void __exit db5500_regulator_exit(void) +{ + platform_driver_unregister(&db5500_regulator_driver); +} + +arch_initcall(db5500_regulator_init); +module_exit(db5500_regulator_exit); + +MODULE_AUTHOR("STMicroelectronics/ST-Ericsson"); +MODULE_DESCRIPTION("DB5500 regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/db8500-prcmu.c b/drivers/regulator/db8500-prcmu.c index 2bb8f451cc0..65e2f83db33 100644 --- a/drivers/regulator/db8500-prcmu.c +++ b/drivers/regulator/db8500-prcmu.c @@ -13,78 +13,15 @@ #include <linux/err.h> #include <linux/spinlock.h> #include <linux/platform_device.h> -#include <linux/mfd/db8500-prcmu.h> +#include <linux/mfd/dbx500-prcmu.h> #include <linux/regulator/driver.h> #include <linux/regulator/machine.h> #include <linux/regulator/db8500-prcmu.h> - -/* - * power state reference count - */ -static int power_state_active_cnt; /* will initialize to zero */ -static DEFINE_SPINLOCK(power_state_active_lock); - -static void power_state_active_enable(void) -{ - unsigned long flags; - - spin_lock_irqsave(&power_state_active_lock, flags); - power_state_active_cnt++; - spin_unlock_irqrestore(&power_state_active_lock, flags); -} - -static int power_state_active_disable(void) -{ - int ret = 0; - unsigned long flags; - - spin_lock_irqsave(&power_state_active_lock, flags); - if (power_state_active_cnt <= 0) { - pr_err("power state: unbalanced enable/disable calls\n"); - ret = -EINVAL; - goto out; - } - - power_state_active_cnt--; -out: - spin_unlock_irqrestore(&power_state_active_lock, flags); - return ret; -} - -/* - * Exported interface for CPUIdle only. This function is called when interrupts - * are turned off. Hence, no locking. - */ -int power_state_active_is_enabled(void) -{ - return (power_state_active_cnt > 0); -} - -/** - * struct db8500_regulator_info - db8500 regulator information - * @dev: device pointer - * @desc: regulator description - * @rdev: regulator device pointer - * @is_enabled: status of the regulator - * @epod_id: id for EPOD (power domain) - * @is_ramret: RAM retention switch for EPOD (power domain) - * @operating_point: operating point (only for vape, to be removed) - * - */ -struct db8500_regulator_info { - struct device *dev; - struct regulator_desc desc; - struct regulator_dev *rdev; - bool is_enabled; - u16 epod_id; - bool is_ramret; - bool exclude_from_power_state; - unsigned int operating_point; -}; +#include "dbx500-prcmu.h" static int db8500_regulator_enable(struct regulator_dev *rdev) { - struct db8500_regulator_info *info = rdev_get_drvdata(rdev); + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); if (info == NULL) return -EINVAL; @@ -101,7 +38,7 @@ static int db8500_regulator_enable(struct regulator_dev *rdev) static int db8500_regulator_disable(struct regulator_dev *rdev) { - struct db8500_regulator_info *info = rdev_get_drvdata(rdev); + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); int ret = 0; if (info == NULL) @@ -119,7 +56,7 @@ static int db8500_regulator_disable(struct regulator_dev *rdev) static int db8500_regulator_is_enabled(struct regulator_dev *rdev) { - struct db8500_regulator_info *info = rdev_get_drvdata(rdev); + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); if (info == NULL) return -EINVAL; @@ -196,7 +133,7 @@ static int disable_epod(u16 epod_id, bool ramret) */ static int db8500_regulator_switch_enable(struct regulator_dev *rdev) { - struct db8500_regulator_info *info = rdev_get_drvdata(rdev); + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); int ret; if (info == NULL) @@ -220,7 +157,7 @@ out: static int db8500_regulator_switch_disable(struct regulator_dev *rdev) { - struct db8500_regulator_info *info = rdev_get_drvdata(rdev); + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); int ret; if (info == NULL) @@ -244,7 +181,7 @@ out: static int db8500_regulator_switch_is_enabled(struct regulator_dev *rdev) { - struct db8500_regulator_info *info = rdev_get_drvdata(rdev); + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); if (info == NULL) return -EINVAL; @@ -265,8 +202,8 @@ static struct regulator_ops db8500_regulator_switch_ops = { /* * Regulator information */ -static struct db8500_regulator_info -db8500_regulator_info[DB8500_NUM_REGULATORS] = { +static struct dbx500_regulator_info +dbx500_regulator_info[DB8500_NUM_REGULATORS] = { [DB8500_REGULATOR_VAPE] = { .desc = { .name = "db8500-vape", @@ -475,12 +412,12 @@ static int __devinit db8500_regulator_probe(struct platform_device *pdev) int i, err; /* register all regulators */ - for (i = 0; i < ARRAY_SIZE(db8500_regulator_info); i++) { - struct db8500_regulator_info *info; + for (i = 0; i < ARRAY_SIZE(dbx500_regulator_info); i++) { + struct dbx500_regulator_info *info; struct regulator_init_data *init_data = &db8500_init_data[i]; /* assign per-regulator data */ - info = &db8500_regulator_info[i]; + info = &dbx500_regulator_info[i]; info->dev = &pdev->dev; /* register with the regulator framework */ @@ -493,7 +430,7 @@ static int __devinit db8500_regulator_probe(struct platform_device *pdev) /* if failing, unregister all earlier regulators */ while (--i >= 0) { - info = &db8500_regulator_info[i]; + info = &dbx500_regulator_info[i]; regulator_unregister(info->rdev); } return err; @@ -502,17 +439,22 @@ static int __devinit db8500_regulator_probe(struct platform_device *pdev) dev_dbg(rdev_get_dev(info->rdev), "regulator-%s-probed\n", info->desc.name); } + err = ux500_regulator_debug_init(pdev, + dbx500_regulator_info, + ARRAY_SIZE(dbx500_regulator_info)); - return 0; + return err; } static int __exit db8500_regulator_remove(struct platform_device *pdev) { int i; - for (i = 0; i < ARRAY_SIZE(db8500_regulator_info); i++) { - struct db8500_regulator_info *info; - info = &db8500_regulator_info[i]; + ux500_regulator_debug_exit(); + + for (i = 0; i < ARRAY_SIZE(dbx500_regulator_info); i++) { + struct dbx500_regulator_info *info; + info = &dbx500_regulator_info[i]; dev_vdbg(rdev_get_dev(info->rdev), "regulator-%s-remove\n", info->desc.name); diff --git a/drivers/regulator/dbx500-prcmu.c b/drivers/regulator/dbx500-prcmu.c new file mode 100644 index 00000000000..fb6976ee7d2 --- /dev/null +++ b/drivers/regulator/dbx500-prcmu.c @@ -0,0 +1,281 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Authors: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson + * Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson + * + * UX500 common part of Power domain regulators + */ + +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/regulator/driver.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/slab.h> + +#include "dbx500-prcmu.h" + +/* + * power state reference count + */ +static int power_state_active_cnt; /* will initialize to zero */ +static DEFINE_SPINLOCK(power_state_active_lock); + +void power_state_active_enable(void) +{ + unsigned long flags; + + spin_lock_irqsave(&power_state_active_lock, flags); + power_state_active_cnt++; + spin_unlock_irqrestore(&power_state_active_lock, flags); +} + +int power_state_active_disable(void) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&power_state_active_lock, flags); + if (power_state_active_cnt <= 0) { + pr_err("power state: unbalanced enable/disable calls\n"); + ret = -EINVAL; + goto out; + } + + power_state_active_cnt--; +out: + spin_unlock_irqrestore(&power_state_active_lock, flags); + return ret; +} + +/* + * Exported interface for CPUIdle only. This function is called when interrupts + * are turned off. Hence, no locking. + */ +int power_state_active_is_enabled(void) +{ + return (power_state_active_cnt > 0); +} + +struct ux500_regulator { + char *name; + void (*enable)(void); + int (*disable)(void); +}; +static struct ux500_regulator ux500_atomic_regulators[] = { + { + .name = "dma40.0", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "ssp0", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "ssp1", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "spi0", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "spi1", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "spi2", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "spi3", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "cryp1", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "hash1", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, +}; + +struct ux500_regulator *__must_check ux500_regulator_get(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ux500_atomic_regulators); i++) { + if (!strcmp(dev_name(dev), ux500_atomic_regulators[i].name)) + return &ux500_atomic_regulators[i]; + } + + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(ux500_regulator_get); + +int ux500_regulator_atomic_enable(struct ux500_regulator *regulator) +{ + if (regulator) { + regulator->enable(); + return 0; + } + return -EINVAL; +} +EXPORT_SYMBOL_GPL(ux500_regulator_atomic_enable); + +int ux500_regulator_atomic_disable(struct ux500_regulator *regulator) +{ + if (regulator) + return regulator->disable(); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(ux500_regulator_atomic_disable); + +void ux500_regulator_put(struct ux500_regulator *regulator) +{ + /* Here for symetric reasons and for possible future use */ +} +EXPORT_SYMBOL_GPL(ux500_regulator_put); + +#ifdef CONFIG_REGULATOR_DEBUG + +static struct ux500_regulator_debug { + struct dentry *dir; + struct dentry *status_file; + struct dbx500_regulator_info *regulator_array; + int num_regulators; + u8 *state_before_suspend; + u8 *state_after_suspend; +} rdebug; + +void ux500_regulator_suspend_debug(void) +{ + int i; + for (i = 0; i < rdebug.num_regulators; i++) + rdebug.state_before_suspend[i] = + rdebug.regulator_array[i].is_enabled; +} + +void ux500_regulator_resume_debug(void) +{ + int i; + for (i = 0; i < rdebug.num_regulators; i++) + rdebug.state_after_suspend[i] = + rdebug.regulator_array[i].is_enabled; +} + +static int ux500_regulator_status_print(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + int err; + int i; + + /* print dump header */ + err = seq_printf(s, "ux500-regulator status:\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow\n"); + + err = seq_printf(s, "%31s : %8s : %8s\n", "current", + "before", "after"); + if (err < 0) + dev_err(dev, "seq_printf overflow\n"); + + for (i = 0; i < rdebug.num_regulators; i++) { + struct dbx500_regulator_info *info; + /* Access per-regulator data */ + info = &rdebug.regulator_array[i]; + + /* print status */ + err = seq_printf(s, "%20s : %8s : %8s : %8s\n", info->desc.name, + info->is_enabled ? "enabled" : "disabled", + rdebug.state_before_suspend[i] ? "enabled" : "disabled", + rdebug.state_after_suspend[i] ? "enabled" : "disabled"); + if (err < 0) + dev_err(dev, "seq_printf overflow\n"); + } + + return 0; +} + +static int ux500_regulator_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, ux500_regulator_status_print, + inode->i_private); +} + +static const struct file_operations ux500_regulator_status_fops = { + .open = ux500_regulator_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +int __devinit +ux500_regulator_debug_init(struct platform_device *pdev, + struct dbx500_regulator_info *regulator_info, + int num_regulators) +{ + /* create directory */ + rdebug.dir = debugfs_create_dir("ux500-regulator", NULL); + if (!rdebug.dir) + goto exit_no_debugfs; + + /* create "status" file */ + rdebug.status_file = debugfs_create_file("status", + S_IRUGO, rdebug.dir, &pdev->dev, + &ux500_regulator_status_fops); + if (!rdebug.status_file) + goto exit_destroy_dir; + + rdebug.regulator_array = regulator_info; + rdebug.num_regulators = num_regulators; + + rdebug.state_before_suspend = kzalloc(num_regulators, GFP_KERNEL); + if (!rdebug.state_before_suspend) { + dev_err(&pdev->dev, + "could not allocate memory for saving state\n"); + goto exit_destory_status; + } + + rdebug.state_after_suspend = kzalloc(num_regulators, GFP_KERNEL); + if (!rdebug.state_after_suspend) { + dev_err(&pdev->dev, + "could not allocate memory for saving state\n"); + goto exit_free; + } + return 0; + +exit_free: + kfree(rdebug.state_before_suspend); +exit_destory_status: + debugfs_remove(rdebug.status_file); +exit_destroy_dir: + debugfs_remove(rdebug.dir); +exit_no_debugfs: + dev_err(&pdev->dev, "failed to create debugfs entries.\n"); + return -ENOMEM; +} + +int __devexit ux500_regulator_debug_exit(void) +{ + debugfs_remove_recursive(rdebug.dir); + kfree(rdebug.state_after_suspend); + kfree(rdebug.state_before_suspend); + + return 0; +} +#endif diff --git a/drivers/regulator/dbx500-prcmu.h b/drivers/regulator/dbx500-prcmu.h new file mode 100644 index 00000000000..f7e20fe075a --- /dev/null +++ b/drivers/regulator/dbx500-prcmu.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Bengt Jonsson <bengt.jonsson@stericsson.com> for ST-Ericsson, + * Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + * License Terms: GNU General Public License v2 + * + */ + +#ifndef DBX500_REGULATOR_H +#define DBX500_REGULATOR_H + +#include <linux/platform_device.h> + +/** + * struct dbx500_regulator_info - dbx500 regulator information + * @dev: device pointer + * @desc: regulator description + * @rdev: regulator device pointer + * @is_enabled: status of the regulator + * @epod_id: id for EPOD (power domain) + * @is_ramret: RAM retention switch for EPOD (power domain) + * @operating_point: operating point (only for vape, to be removed) + * + */ +struct dbx500_regulator_info { + struct device *dev; + struct regulator_desc desc; + struct regulator_dev *rdev; + bool is_enabled; + u16 epod_id; + bool is_ramret; + bool exclude_from_power_state; + unsigned int operating_point; +}; + +void power_state_active_enable(void); +int power_state_active_disable(void); + + +#ifdef CONFIG_REGULATOR_DEBUG +int ux500_regulator_debug_init(struct platform_device *pdev, + struct dbx500_regulator_info *regulator_info, + int num_regulators); + +int ux500_regulator_debug_exit(void); +#else + +static inline int ux500_regulator_debug_init(struct platform_device *pdev, + struct dbx500_regulator_info *regulator_info, + int num_regulators) {} + +static inline int ux500_regulator_debug_exit(void) {} +#endif +#endif diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 5a538fc1cc8..15d4eebd734 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -697,6 +697,13 @@ config RTC_DRV_PCF50633 If you say yes here you get support for the RTC subsystem of the NXP PCF50633 used in embedded systems. +config RTC_DRV_AB + tristate "ST-Ericsson AB5500 RTC" + depends on AB5500_CORE + help + Select this to enable the ST-Ericsson AB5500 Mixed Signal IC RTC + support. This chip contains a battery- and capacitor-backed RTC. + config RTC_DRV_AB3100 tristate "ST-Ericsson AB3100 RTC" depends on AB3100_CORE diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 6e6982335c1..a69992dd1cb 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -16,6 +16,7 @@ rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o # Keep the list ordered. obj-$(CONFIG_RTC_DRV_88PM860X) += rtc-88pm860x.o +obj-$(CONFIG_RTC_DRV_AB) += rtc-ab.o obj-$(CONFIG_RTC_DRV_AB3100) += rtc-ab3100.o obj-$(CONFIG_RTC_DRV_AB8500) += rtc-ab8500.o obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o diff --git a/drivers/rtc/rtc-ab.c b/drivers/rtc/rtc-ab.c new file mode 100644 index 00000000000..db1992632fa --- /dev/null +++ b/drivers/rtc/rtc-ab.c @@ -0,0 +1,483 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL) version 2 + * Author: Rabin Vincent <rabin.vincent@stericsson.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> + +#define AB5500_RTC_CLOCK_RATE 32768 +#define AB5500_RTC 0x00 +#define AB5500_RTC_ALARM (1 << 1) +#define AB5500_READREQ 0x01 +#define AB5500_READREQ_REQ 0x01 +#define AB5500_AL0 0x02 +#define AB5500_TI0 0x06 + +/** + * struct ab_rtc - variant specific data + * @irqname: optional name for the alarm interrupt resource + * @epoch: epoch to adjust year to + * @bank: AB bank where this block is present + * @rtc: address of the "RTC" (control) register + * @rtc_alarmon: mask of the alarm enable bit in the above register + * @ti0: address of the TI0 register. The rest of the TI + * registers are assumed to contiguously follow this one. + * @nr_ti: number of TI* registers + * @al0: address of the AL0 register. The rest of the + * AL registers are assumed to contiguously follow this one. + * @nr_al: number of AL* registers + * @startup: optional function to initialize the RTC + * @alarm_to_regs: function to convert alarm time in seconds + * to a list of AL register values + * @time_to_regs: function to convert alarm time in seconds + * to a list of TI register values + * @regs_to_alarm: function to convert a list of AL register + * values to the alarm time in seconds + * @regs_to_time: function to convert a list of TI register + * values to the alarm time in seconds + * @request_read: optional function to request a read from the TI* registers + * @request_write: optional function to request a write to the TI* registers + */ +struct ab_rtc { + const char *irqname; + unsigned int epoch; + + u8 bank; + u8 rtc; + u8 rtc_alarmon; + u8 ti0; + int nr_ti; + u8 al0; + int nr_al; + + int (*startup)(struct device *dev); + void (*alarm_to_regs)(struct device *dev, unsigned long secs, u8 *regs); + void (*time_to_regs)(struct device *dev, unsigned long secs, u8 *regs); + unsigned long (*regs_to_alarm)(struct device *dev, u8 *regs); + unsigned long (*regs_to_time)(struct device *dev, u8 *regs); + int (*request_read)(struct device *dev); + int (*request_write)(struct device *dev); +}; + +static const struct ab_rtc *to_ab_rtc(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + return (struct ab_rtc *)pdev->id_entry->driver_data; +} + +/* Calculate the number of seconds since year, for epoch adjustment */ +static unsigned long ab_rtc_get_elapsed_seconds(unsigned int year) +{ + unsigned long secs; + struct rtc_time tm = { + .tm_year = year - 1900, + .tm_mday = 1, + }; + + rtc_tm_to_time(&tm, &secs); + + return secs; +} + +static int ab5500_rtc_request_read(struct device *dev) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + unsigned long timeout; + int err; + + err = abx500_set_register_interruptible(dev, variant->bank, + AB5500_READREQ, + AB5500_READREQ_REQ); + if (err < 0) + return err; + + timeout = jiffies + HZ; + while (time_before(jiffies, timeout)) { + u8 value; + + err = abx500_get_register_interruptible(dev, variant->bank, + AB5500_READREQ, &value); + if (err < 0) + return err; + + if (!(value & AB5500_READREQ_REQ)) + return 0; + + msleep(1); + } + + return -EIO; +} + +static void +ab5500_rtc_time_to_regs(struct device *dev, unsigned long secs, u8 *regs) +{ + unsigned long mins = secs / 60; + u64 fat_time; + + secs %= 60; + + fat_time = secs * AB5500_RTC_CLOCK_RATE; + fat_time |= (u64)mins << 21; + + regs[0] = (fat_time) & 0xFF; + regs[1] = (fat_time >> 8) & 0xFF; + regs[2] = (fat_time >> 16) & 0xFF; + regs[3] = (fat_time >> 24) & 0xFF; + regs[4] = (fat_time >> 32) & 0xFF; + regs[5] = (fat_time >> 40) & 0xFF; +} + +static unsigned long +ab5500_rtc_regs_to_time(struct device *dev, u8 *regs) +{ + u64 fat_time = ((u64)regs[5] << 40) | ((u64)regs[4] << 32) | + ((u64)regs[3] << 24) | ((u64)regs[2] << 16) | + ((u64)regs[1] << 8) | regs[0]; + unsigned long secs = (fat_time & 0x1fffff) / AB5500_RTC_CLOCK_RATE; + unsigned long mins = fat_time >> 21; + + return mins * 60 + secs; +} + +static void +ab5500_rtc_alarm_to_regs(struct device *dev, unsigned long secs, u8 *regs) +{ + unsigned long mins = secs / 60; + +#ifdef CONFIG_ANDROID + /* + * Needed because Android believes all hw have a wake-up resolution in + * seconds. + */ + mins++; +#endif + + regs[0] = mins & 0xFF; + regs[1] = (mins >> 8) & 0xFF; + regs[2] = (mins >> 16) & 0xFF; +} + +static unsigned long +ab5500_rtc_regs_to_alarm(struct device *dev, u8 *regs) +{ + unsigned long mins = ((unsigned long)regs[2] << 16) | + ((unsigned long)regs[1] << 8) | + regs[0]; + unsigned long secs = mins * 60; + + return secs; +} + +static const struct ab_rtc ab5500_rtc = { + .irqname = "RTC_Alarm", + .bank = AB5500_BANK_RTC, + .rtc = AB5500_RTC, + .rtc_alarmon = AB5500_RTC_ALARM, + .ti0 = AB5500_TI0, + .nr_ti = 6, + .al0 = AB5500_AL0, + .nr_al = 3, + .epoch = 2000, + .time_to_regs = ab5500_rtc_time_to_regs, + .regs_to_time = ab5500_rtc_regs_to_time, + .alarm_to_regs = ab5500_rtc_alarm_to_regs, + .regs_to_alarm = ab5500_rtc_regs_to_alarm, + .request_read = ab5500_rtc_request_read, +}; + +static int ab_rtc_request_read(struct device *dev) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + + if (!variant->request_read) + return 0; + + return variant->request_read(dev); +} + +static int ab_rtc_request_write(struct device *dev) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + + if (!variant->request_write) + return 0; + + return variant->request_write(dev); +} + +static bool ab_rtc_valid_time(struct device *dev, struct rtc_time *time) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + + if (!variant->epoch) + return true; + + return time->tm_year >= variant->epoch - 1900; +} + +static int +ab_rtc_tm_to_time(struct device *dev, struct rtc_time *tm, unsigned long *secs) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + + rtc_tm_to_time(tm, secs); + + if (variant->epoch) + *secs -= ab_rtc_get_elapsed_seconds(variant->epoch); + + return 0; +} + +static int +ab_rtc_time_to_tm(struct device *dev, unsigned long secs, struct rtc_time *tm) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + + if (variant->epoch) + secs += ab_rtc_get_elapsed_seconds(variant->epoch); + + rtc_time_to_tm(secs, tm); + + return 0; +} + +static int ab_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + unsigned char buf[variant->nr_ti]; + unsigned long secs; + int err; + + err = ab_rtc_request_read(dev); + if (err) + return err; + + err = abx500_get_register_page_interruptible(dev, variant->bank, + variant->ti0, + buf, variant->nr_ti); + if (err) + return err; + + secs = variant->regs_to_time(dev, buf); + ab_rtc_time_to_tm(dev, secs, tm); + + return rtc_valid_tm(tm); +} + +static int ab_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + unsigned char buf[variant->nr_ti]; + unsigned long secs; + u8 reg = variant->ti0; + int err; + int i; + + if (!ab_rtc_valid_time(dev, tm)) + return -EINVAL; + + ab_rtc_tm_to_time(dev, tm, &secs); + variant->time_to_regs(dev, secs, buf); + + for (i = 0; i < variant->nr_ti; i++, reg++) { + err = abx500_set_register_interruptible(dev, variant->bank, + reg, buf[i]); + if (err) + return err; + } + + return ab_rtc_request_write(dev); +} + +static int ab_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + unsigned long secs; + u8 buf[variant->nr_al]; + u8 rtcval; + int err; + + err = abx500_get_register_interruptible(dev, variant->bank, + variant->rtc, &rtcval); + if (err) + return err; + + alarm->enabled = !!(rtcval & variant->rtc_alarmon); + alarm->pending = 0; + + err = abx500_get_register_page_interruptible(dev, variant->bank, + variant->al0, buf, + variant->nr_al); + if (err) + return err; + + secs = variant->regs_to_alarm(dev, buf); + ab_rtc_time_to_tm(dev, secs, &alarm->time); + + return rtc_valid_tm(&alarm->time); +} + +static int ab_rtc_alarm_enable(struct device *dev, unsigned int enabled) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + u8 mask = variant->rtc_alarmon; + u8 value = enabled ? mask : 0; + + return abx500_mask_and_set_register_interruptible(dev, variant->bank, + variant->rtc, mask, + value); +} + +static int ab_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + unsigned char buf[variant->nr_al]; + unsigned long secs; + u8 reg = variant->al0; + int err; + int i; + + if (!ab_rtc_valid_time(dev, &alarm->time)) + return -EINVAL; + + ab_rtc_tm_to_time(dev, &alarm->time, &secs); + variant->alarm_to_regs(dev, secs, buf); + + /* + * Disable alarm first. Otherwise the RTC may not detect an alarm + * reprogrammed for the same time without disabling the alarm in + * between the programmings. + */ + err = ab_rtc_alarm_enable(dev, false); + if (err) + return err; + + for (i = 0; i < variant->nr_al; i++, reg++) { + err = abx500_set_register_interruptible(dev, variant->bank, + reg, buf[i]); + if (err) + return err; + } + + return ab_rtc_alarm_enable(dev, true); +} + +static const struct rtc_class_ops ab_rtc_ops = { + .read_time = ab_rtc_read_time, + .set_time = ab_rtc_set_time, + .read_alarm = ab_rtc_read_alarm, + .set_alarm = ab_rtc_set_alarm, + .alarm_irq_enable = ab_rtc_alarm_enable, +}; + +static irqreturn_t ab_rtc_irq(int irq, void *dev_id) +{ + unsigned long events = RTC_IRQF | RTC_AF; + struct rtc_device *rtc = dev_id; + + rtc_update_irq(rtc, 1, events); + + return IRQ_HANDLED; +} + +static int __devinit ab_rtc_probe(struct platform_device *pdev) +{ + const struct ab_rtc *variant = to_ab_rtc(&pdev->dev); + int err; + struct rtc_device *rtc; + int irq = -ENXIO; + + if (variant->irqname) { + irq = platform_get_irq_byname(pdev, variant->irqname); + if (irq < 0) + return irq; + } + + if (variant->startup) { + err = variant->startup(&pdev->dev); + if (err) + return err; + } + + rtc = rtc_device_register("ab8500-rtc", &pdev->dev, &ab_rtc_ops, + THIS_MODULE); + if (IS_ERR(rtc)) { + dev_err(&pdev->dev, "Registration failed\n"); + err = PTR_ERR(rtc); + return err; + } + + if (irq >= 0) { + err = request_any_context_irq(irq, ab_rtc_irq, + IRQF_NO_SUSPEND, + pdev->id_entry->name, + rtc); + if (err < 0) { + dev_err(&pdev->dev, "could not get irq: %d\n", err); + goto out_unregister; + } + } + + platform_set_drvdata(pdev, rtc); + + return 0; + +out_unregister: + rtc_device_unregister(rtc); + return err; +} + +static int __devexit ab_rtc_remove(struct platform_device *pdev) +{ + const struct ab_rtc *variant = to_ab_rtc(&pdev->dev); + struct rtc_device *rtc = platform_get_drvdata(pdev); + int irq = platform_get_irq_byname(pdev, variant->irqname); + + if (irq >= 0) + free_irq(irq, rtc); + rtc_device_unregister(rtc); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_device_id ab_rtc_id_table[] = { + { "ab5500-rtc", (kernel_ulong_t)&ab5500_rtc, }, + { }, +}; +MODULE_DEVICE_TABLE(platform, ab_rtc_id_table); + +static struct platform_driver ab_rtc_driver = { + .driver.name = "ab-rtc", + .driver.owner = THIS_MODULE, + .id_table = ab_rtc_id_table, + .probe = ab_rtc_probe, + .remove = __devexit_p(ab_rtc_remove), +}; + +static int __init ab_rtc_init(void) +{ + return platform_driver_register(&ab_rtc_driver); +} +module_init(ab_rtc_init); + +static void __exit ab_rtc_exit(void) +{ + platform_driver_unregister(&ab_rtc_driver); +} +module_exit(ab_rtc_exit); + +MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>"); +MODULE_DESCRIPTION("AB5500 RTC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rtc/rtc-ab8500.c b/drivers/rtc/rtc-ab8500.c index e346705aae9..3160d9b5613 100644 --- a/drivers/rtc/rtc-ab8500.c +++ b/drivers/rtc/rtc-ab8500.c @@ -90,7 +90,7 @@ static int ab8500_rtc_read_time(struct device *dev, struct rtc_time *tm) /* Early AB8500 chips will not clear the rtc read request bit */ if (abx500_get_chip_id(dev) == 0) { - msleep(1); + mdelay(1); } else { /* Wait for some cycles after enabling the rtc read in ab8500 */ while (time_before(jiffies, timeout)) { @@ -102,7 +102,7 @@ static int ab8500_rtc_read_time(struct device *dev, struct rtc_time *tm) if (!(value & RTC_READ_REQUEST)) break; - msleep(1); + mdelay(1); } } @@ -241,8 +241,19 @@ static int ab8500_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) */ secs -= get_elapsed_seconds(AB8500_RTC_EPOCH); +#ifndef CONFIG_ANDROID + secs += 30; /* Round to nearest minute */ +#endif + mins = secs / 60; +#ifdef CONFIG_ANDROID + /* + * Needed due to Android believes all hw have a wake-up resolution + * in seconds. + */ + mins++; +#endif buf[2] = mins & 0xFF; buf[1] = (mins >> 8) & 0xFF; buf[0] = (mins >> 16) & 0xFF; @@ -258,6 +269,106 @@ static int ab8500_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) return ab8500_rtc_irq_enable(dev, alarm->enabled); } + +static int ab8500_rtc_set_calibration(struct device *dev, int calibration) +{ + int retval; + u8 rtccal = 0; + + /* + * Check that the calibration value (which is in units of 0.5 parts-per-million) + * is in the AB8500's range for RtcCalibration register. + */ + if ((calibration < -127) || (calibration > 127)) { + dev_err(dev, "RtcCalibration value outside permitted range\n"); + return -EINVAL; + } + + /* + * The AB8500 uses sign (in bit7) and magnitude (in bits0-7) + * so need to convert to this sort of representation before writing + * into RtcCalibration register... + */ + if (calibration >= 0) + rtccal = 0x7F & calibration; + else + rtccal = ~(calibration -1) | 0x80; + + retval = abx500_set_register_interruptible(dev, AB8500_RTC, + AB8500_RTC_CALIB_REG, rtccal); + + return retval; +} + +static int ab8500_rtc_get_calibration(struct device *dev, int *calibration) +{ + int retval; + u8 rtccal = 0; + + retval = abx500_get_register_interruptible(dev, AB8500_RTC, + AB8500_RTC_CALIB_REG, &rtccal); + if (retval >= 0) { + /* + * The AB8500 uses sign (in bit7) and magnitude (in bits0-7) + * so need to convert value from RtcCalibration register into + * a two's complement signed value... + */ + if (rtccal & 0x80) + *calibration = 0 - (rtccal & 0x7F); + else + *calibration = 0x7F & rtccal; + } + + return retval; +} + +static ssize_t ab8500_sysfs_store_rtc_calibration(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int retval; + int calibration = 0; + + if (sscanf(buf, " %i ", &calibration) != 1) { + dev_err(dev, "Failed to store RTC calibration attribute\n"); + return -EINVAL; + } + + retval = ab8500_rtc_set_calibration(dev, calibration); + + return retval ? retval : count; +} + +static ssize_t ab8500_sysfs_show_rtc_calibration(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval = 0; + int calibration = 0; + + retval = ab8500_rtc_get_calibration(dev, &calibration); + if (retval < 0) { + dev_err(dev, "Failed to read RTC calibration attribute\n"); + sprintf(buf, "0\n"); + return retval; + } + + return sprintf(buf, "%d\n", calibration); +} + +static DEVICE_ATTR(rtc_calibration, S_IRUGO | S_IWUSR, + ab8500_sysfs_show_rtc_calibration, + ab8500_sysfs_store_rtc_calibration); + +static int ab8500_sysfs_rtc_register(struct device *dev) +{ + return device_create_file(dev, &dev_attr_rtc_calibration); +} + +static void ab8500_sysfs_rtc_unregister(struct device *dev) +{ + device_remove_file(dev, &dev_attr_rtc_calibration); +} + static irqreturn_t rtc_alarm_handler(int irq, void *data) { struct rtc_device *rtc = data; @@ -295,7 +406,7 @@ static int __devinit ab8500_rtc_probe(struct platform_device *pdev) return err; /* Wait for reset by the PorRtc */ - msleep(1); + mdelay(1); err = abx500_get_register_interruptible(&pdev->dev, AB8500_RTC, AB8500_RTC_STAT_REG, &rtc_ctrl); @@ -308,6 +419,8 @@ static int __devinit ab8500_rtc_probe(struct platform_device *pdev) return -ENODEV; } + device_init_wakeup(&pdev->dev, true); + rtc = rtc_device_register("ab8500-rtc", &pdev->dev, &ab8500_rtc_ops, THIS_MODULE); if (IS_ERR(rtc)) { @@ -316,8 +429,8 @@ static int __devinit ab8500_rtc_probe(struct platform_device *pdev) return err; } - err = request_threaded_irq(irq, NULL, rtc_alarm_handler, 0, - "ab8500-rtc", rtc); + err = request_threaded_irq(irq, NULL, rtc_alarm_handler, + IRQF_NO_SUSPEND, "ab8500-rtc", rtc); if (err < 0) { rtc_device_unregister(rtc); return err; @@ -325,6 +438,13 @@ static int __devinit ab8500_rtc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, rtc); + + err = ab8500_sysfs_rtc_register(&pdev->dev); + if (err) { + dev_err(&pdev->dev, "sysfs RTC failed to register\n"); + return err; + } + return 0; } @@ -333,6 +453,8 @@ static int __devexit ab8500_rtc_remove(struct platform_device *pdev) struct rtc_device *rtc = platform_get_drvdata(pdev); int irq = platform_get_irq_byname(pdev, "ALARM"); + ab8500_sysfs_rtc_unregister(&pdev->dev); + free_irq(irq, rtc); rtc_device_unregister(rtc); platform_set_drvdata(pdev, NULL); diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 730b4a37b82..dddf1265d11 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -514,7 +514,12 @@ static void giveback(struct pl022 *pl022) msg->state = NULL; if (msg->complete) msg->complete(msg->context); - /* This message is completed, so let's turn off the clocks & power */ + + /* disable the SPI/SSP operation */ + writew((readw(SSP_CR1(pl022->virtbase)) & + (~SSP_CR1_MASK_SSE)), SSP_CR1(pl022->virtbase)); + + /* This message is completed, so let's turn off the clock! */ clk_disable(pl022->clk); amba_pclk_disable(pl022->adev); amba_vcore_disable(pl022->adev); @@ -924,6 +929,12 @@ static int configure_dma(struct pl022 *pl022) struct dma_async_tx_descriptor *rxdesc; struct dma_async_tx_descriptor *txdesc; + /* DMA burstsize should be same as the FIFO trigger level */ + rx_conf.src_maxburst = pl022->rx_lev_trig ? 1 << + (pl022->rx_lev_trig + 1) : pl022->rx_lev_trig; + tx_conf.dst_maxburst = pl022->tx_lev_trig ? 1 << + (pl022->tx_lev_trig + 1) : pl022->tx_lev_trig; + /* Check that the channels are available */ if (!rxchan || !txchan) return -ENODEV; @@ -1547,6 +1558,7 @@ static void pump_messages(struct work_struct *work) pm_runtime_get_sync(&pl022->adev->dev); amba_vcore_enable(pl022->adev); amba_pclk_enable(pl022->adev); + pm_runtime_get_sync(&pl022->adev->dev); clk_enable(pl022->clk); restore_state(pl022); flush(pl022); @@ -1896,7 +1908,7 @@ static int pl022_setup(struct spi_device *spi) { struct pl022_config_chip const *chip_info; struct chip_data *chip; - struct ssp_clock_params clk_freq = {0, }; + struct ssp_clock_params clk_freq = { .cpsdvsr = 0, .scr = 0}; int status = 0; struct pl022 *pl022 = spi_master_get_devdata(spi->master); unsigned int bits = spi->bits_per_word; @@ -2189,6 +2201,9 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id) pm_runtime_enable(dev); pm_runtime_resume(dev); + pm_runtime_enable(dev); + pm_runtime_resume(dev); + pl022->clk = clk_get(&adev->dev, NULL); if (IS_ERR(pl022->clk)) { status = PTR_ERR(pl022->clk); @@ -2279,6 +2294,7 @@ pl022_remove(struct amba_device *adev) free_irq(adev->irq[0], pl022); clk_disable(pl022->clk); clk_put(pl022->clk); + pm_runtime_disable(&adev->dev); iounmap(pl022->virtbase); amba_release_regions(adev); tasklet_disable(&pl022->pump_transfers); @@ -2300,11 +2316,6 @@ static int pl022_suspend(struct amba_device *adev, pm_message_t state) return status; } - amba_vcore_enable(adev); - amba_pclk_enable(adev); - load_ssp_default_config(pl022); - amba_pclk_disable(adev); - amba_vcore_disable(adev); dev_dbg(&adev->dev, "suspended\n"); return 0; } diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 4dcb37bbdf9..6deb387415e 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -323,6 +323,14 @@ config SERIAL_AMBA_PL011_CONSOLE your boot loader (lilo or loadlin) about how to pass options to the kernel at boot time.) +config SERIAL_AMBA_PL011_CLOCK_CONTROL + bool "Support for clock control on AMBA serial port" + depends on SERIAL_AMBA_PL011 + select CONSOLE_POLL + ---help--- + Say Y here if you wish to use amba set_termios function to control + the pl011 clock. Any positive baudrate passed enables clock, + config SERIAL_SB1250_DUART tristate "BCM1xxx on-chip DUART serial support" depends on SIBYTE_SB1xxx_SOC=y diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index f5f6831b0a6..7035910de79 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -47,12 +47,13 @@ #include <linux/amba/serial.h> #include <linux/clk.h> #include <linux/slab.h> +#include <linux/regulator/consumer.h> #include <linux/dmaengine.h> #include <linux/dma-mapping.h> #include <linux/scatterlist.h> #include <linux/delay.h> +#include <linux/io.h> -#include <asm/io.h> #include <asm/sizes.h> #define UART_NR 14 @@ -63,9 +64,41 @@ #define AMBA_ISR_PASS_LIMIT 256 -#define UART_DR_ERROR (UART011_DR_OE|UART011_DR_BE|UART011_DR_PE|UART011_DR_FE) +#define UART_DR_ERROR (UART011_DR_OE | UART011_DR_BE | \ + UART011_DR_PE | UART011_DR_FE) #define UART_DUMMY_DR_RX (1 << 16) +/* + * The console UART is handled differently for power management (it doesn't + * take the regulator, in order to allow the system to go to sleep even if the + * console is open). This should be removed once cable detect is in place. + */ +#ifdef CONFIG_SERIAL_CORE_CONSOLE +#define uart_console(port) ((port)->cons \ + && (port)->cons->index == (port)->line) +#else +#define uart_console(port) (0) +#endif + +/* Available amba pl011 port clock states */ +enum pl011_clk_states { + PL011_CLK_OFF = 0, /* clock disabled */ + PL011_CLK_REQUEST_OFF, /* disable after TX flushed */ + PL011_CLK_ON, /* clock enabled */ + PL011_PORT_OFF, /* port disabled */ +}; + +/* + * Backup registers to be used during regulator startup/shutdown + */ +static const u32 backup_regs[] = { + UART011_IBRD, + UART011_FBRD, + ST_UART011_LCRH_RX, + ST_UART011_LCRH_TX, + UART011_CR, + UART011_IMSC, +}; #define UART_WA_SAVE_NR 14 @@ -157,9 +190,17 @@ struct uart_amba_port { unsigned int im; /* interrupt mask */ unsigned int old_status; unsigned int fifosize; /* vendor-specific */ + unsigned int ifls; /* vendor-specific */ unsigned int lcrh_tx; /* vendor-specific */ unsigned int lcrh_rx; /* vendor-specific */ bool autorts; +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL + enum pl011_clk_states clk_state; /* actual clock state */ + struct delayed_work clk_off_work; /* work used for clock off */ + unsigned int clk_off_delay; /* clock off delay */ +#endif + struct regulator *regulator; + u32 backup[ARRAY_SIZE(backup_regs)]; char type[12]; bool interrupt_may_hang; /* vendor-specific */ #ifdef CONFIG_DMA_ENGINE @@ -305,7 +346,8 @@ static void pl011_dma_probe_initcall(struct uart_amba_port *uap) .src_maxburst = uap->fifosize >> 1, }; - chan = dma_request_channel(mask, plat->dma_filter, plat->dma_rx_param); + chan = dma_request_channel(mask, + plat->dma_filter, plat->dma_rx_param); if (!chan) { dev_err(uap->port.dev, "no RX DMA channel!\n"); return; @@ -749,8 +791,9 @@ static void pl011_dma_rx_chars(struct uart_amba_port *uap, */ if (dma_count == pending && readfifo) { /* Clear any error flags */ - writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS, - uap->port.membase + UART011_ICR); + writew(UART011_OEIS | UART011_BEIS | + UART011_PEIS | UART011_FEIS, + uap->port.membase + UART011_ICR); /* * If we read all the DMA'd characters, and we had an @@ -947,11 +990,13 @@ static void pl011_dma_shutdown(struct uart_amba_port *uap) spin_unlock_irq(&uap->port.lock); if (uap->using_tx_dma) { - /* In theory, this should already be done by pl011_dma_flush_buffer */ + /* In theory, this should already be done by + * pl011_dma_flush_buffer + */ dmaengine_terminate_all(uap->dmatx.chan); if (uap->dmatx.queued) { - dma_unmap_sg(uap->dmatx.chan->device->dev, &uap->dmatx.sg, 1, - DMA_TO_DEVICE); + dma_unmap_sg(uap->dmatx.chan->device->dev, + &uap->dmatx.sg, 1, DMA_TO_DEVICE); uap->dmatx.queued = false; } @@ -962,8 +1007,10 @@ static void pl011_dma_shutdown(struct uart_amba_port *uap) if (uap->using_rx_dma) { dmaengine_terminate_all(uap->dmarx.chan); /* Clean up the RX DMA */ - pl011_sgbuf_free(uap->dmarx.chan, &uap->dmarx.sgbuf_a, DMA_FROM_DEVICE); - pl011_sgbuf_free(uap->dmarx.chan, &uap->dmarx.sgbuf_b, DMA_FROM_DEVICE); + pl011_sgbuf_free(uap->dmarx.chan, &uap->dmarx.sgbuf_a, + DMA_FROM_DEVICE); + pl011_sgbuf_free(uap->dmarx.chan, &uap->dmarx.sgbuf_b, + DMA_FROM_DEVICE); uap->using_rx_dma = false; } } @@ -1100,6 +1147,246 @@ static void pl011_lockup_wa(unsigned long data) tty->hw_stopped = 0; } +static void __pl011_startup(struct uart_amba_port *uap) +{ + unsigned int cr; + + writew(uap->ifls, uap->port.membase + UART011_IFLS); + + /* + * Provoke TX FIFO interrupt into asserting. + */ + cr = UART01x_CR_UARTEN | UART011_CR_TXE | UART011_CR_LBE; + writew(cr, uap->port.membase + UART011_CR); + writew(0, uap->port.membase + UART011_FBRD); + writew(1, uap->port.membase + UART011_IBRD); + writew(0, uap->port.membase + uap->lcrh_rx); + if (uap->lcrh_tx != uap->lcrh_rx) { + int i; + /* + * Wait 10 PCLKs before writing LCRH_TX register, + * to get this delay write read only register 10 times + */ + for (i = 0; i < 10; ++i) + writew(0xff, uap->port.membase + UART011_MIS); + writew(0, uap->port.membase + uap->lcrh_tx); + } + writew(0, uap->port.membase + UART01x_DR); + while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY) + barrier(); +} + +/* Backup the registers during regulator startup/shutdown */ +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL +static int pl011_backup(struct uart_amba_port *uap, bool suspend) +{ + int i, cnt; + + if (!suspend) { + __pl011_startup(uap); + writew(0, uap->port.membase + UART011_CR); + } + + for (i = 0; i < ARRAY_SIZE(backup_regs); i++) { + if (suspend) + uap->backup[i] = readw(uap->port.membase + + backup_regs[i]); + else { + if (backup_regs[i] == ST_UART011_LCRH_TX) { + /* + * Wait 10 PCLKs before writing LCRH_TX + * register, to get this delay write read + * only register 10 times + */ + for (cnt = 0; cnt < 10; ++cnt) + writew(0xff, uap->port.membase + + UART011_MIS); + } + + writew(uap->backup[i], + uap->port.membase + backup_regs[i]); + } + } + return 0; +} +#endif + +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL +/* Turn clock off if TX buffer is empty, otherwise reschedule */ +static void pl011_clock_off(struct work_struct *work) +{ + struct uart_amba_port *uap = container_of(work, struct uart_amba_port, + clk_off_work.work); + struct uart_port *port = &uap->port; + struct circ_buf *xmit = &port->state->xmit; + unsigned long flags; + bool disable_regulator = false; + unsigned int busy, interrupt_status; + + spin_lock_irqsave(&port->lock, flags); + + interrupt_status = readw(uap->port.membase + UART011_MIS); + busy = readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY; + + if (uap->clk_state == PL011_CLK_REQUEST_OFF) { + if (uart_circ_empty(xmit) && !interrupt_status && !busy) { + if (!uart_console(&uap->port) && uap->regulator) { + pl011_backup(uap, true); + disable_regulator = true; + } + uap->clk_state = PL011_CLK_OFF; + clk_disable(uap->clk); + } else + schedule_delayed_work(&uap->clk_off_work, + uap->clk_off_delay); + } + + spin_unlock_irqrestore(&port->lock, flags); + + if (disable_regulator) + regulator_disable(uap->regulator); +} + +/* Request to turn off uart clock once pending TX is flushed */ +static void pl011_clock_request_off(struct uart_port *port) +{ + unsigned long flags; + struct uart_amba_port *uap = (struct uart_amba_port *)(port); + + spin_lock_irqsave(&port->lock, flags); + + if (uap->clk_state == PL011_CLK_ON) { + uap->clk_state = PL011_CLK_REQUEST_OFF; + /* Turn off later */ + schedule_delayed_work(&uap->clk_off_work, + uap->clk_off_delay); + } + + spin_unlock_irqrestore(&port->lock, flags); +} + +/* Request to immediately turn on uart clock */ +static void pl011_clock_on(struct uart_port *port) +{ + unsigned long flags; + struct uart_amba_port *uap = (struct uart_amba_port *)(port); + + spin_lock_irqsave(&port->lock, flags); + + switch (uap->clk_state) { + case PL011_CLK_OFF: + clk_enable(uap->clk); + if (!uart_console(&uap->port) && uap->regulator) { + spin_unlock_irqrestore(&port->lock, flags); + regulator_enable(uap->regulator); + spin_lock_irqsave(&port->lock, flags); + pl011_backup(uap, false); + } + /* fallthrough */ + case PL011_CLK_REQUEST_OFF: + cancel_delayed_work(&uap->clk_off_work); + uap->clk_state = PL011_CLK_ON; + break; + default: + break; + } + + spin_unlock_irqrestore(&port->lock, flags); +} + +static void pl011_clock_check(struct uart_amba_port *uap) +{ + /* Reshedule work during off request */ + if (uap->clk_state == PL011_CLK_REQUEST_OFF) + /* New TX - restart work */ + if (cancel_delayed_work(&uap->clk_off_work)) + schedule_delayed_work(&uap->clk_off_work, + uap->clk_off_delay); +} + +static int pl011_power_startup(struct uart_amba_port *uap) +{ + int retval = 0; + + if (uap->clk_state == PL011_PORT_OFF) { + if (!uart_console(&uap->port) && uap->regulator) + regulator_enable(uap->regulator); + retval = clk_enable(uap->clk); + if (!retval) + uap->clk_state = PL011_CLK_ON; + else + uap->clk_state = PL011_PORT_OFF; + } + + return retval; +} + +static void pl011_power_shutdown(struct uart_amba_port *uap) +{ + bool disable_regulator = false; + + cancel_delayed_work_sync(&uap->clk_off_work); + + spin_lock_irq(&uap->port.lock); + if (uap->clk_state == PL011_CLK_ON || + uap->clk_state == PL011_CLK_REQUEST_OFF) { + clk_disable(uap->clk); + if (!uart_console(&uap->port) && uap->regulator) + disable_regulator = true; + } + uap->clk_state = PL011_PORT_OFF; + spin_unlock_irq(&uap->port.lock); + + if (disable_regulator) + regulator_disable(uap->regulator); +} + +static void +pl011_clock_control(struct uart_port *port, struct ktermios *termios, + struct ktermios *old) +{ + speed_t new_baud = tty_termios_baud_rate(termios); + + if (new_baud == 0) + pl011_clock_request_off(port); + else + pl011_clock_on(port); +} + +static void pl011_clock_control_init(struct uart_amba_port *uap) +{ + uap->clk_state = PL011_PORT_OFF; + INIT_DELAYED_WORK(&uap->clk_off_work, pl011_clock_off); + uap->clk_off_delay = HZ / 10; /* 100 ms */ +} + +#else +/* Blank functions for clock control */ +static inline void pl011_clock_check(struct uart_amba_port *uap) +{ +} + +static inline int pl011_power_startup(struct uart_amba_port *uap) +{ + return clk_enable(uap->clk); +} + +static inline void pl011_power_shutdown(struct uart_amba_port *uap) +{ + clk_disable(uap->clk); +} + +static inline void +pl011_clock_control(struct uart_port *port, struct ktermios *termios, + struct ktermios *old) +{ +} + +static inline void pl011_clock_control_init(struct uart_amba_port *uap) +{ +} +#endif + static void pl011_stop_tx(struct uart_port *port) { struct uart_amba_port *uap = (struct uart_amba_port *)port; @@ -1191,6 +1478,9 @@ static void pl011_tx_chars(struct uart_amba_port *uap) break; } while (--count > 0); + if (count) + pl011_clock_check(uap); + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&uap->port); @@ -1236,7 +1526,7 @@ static irqreturn_t pl011_int(int irq, void *dev_id) do { writew(status & ~(UART011_TXIS|UART011_RTIS| UART011_RXIS), - uap->port.membase + UART011_ICR); + uap->port.membase + UART011_ICR); if (status & (UART011_RTIS|UART011_RXIS)) { if (pl011_dma_rx_running(uap)) @@ -1281,7 +1571,7 @@ static unsigned int pl01x_get_mctrl(struct uart_port *port) #define TIOCMBIT(uartbit, tiocmbit) \ if (status & uartbit) \ - result |= tiocmbit + (result |= tiocmbit) TIOCMBIT(UART01x_FR_DCD, TIOCM_CAR); TIOCMBIT(UART01x_FR_DSR, TIOCM_DSR); @@ -1299,10 +1589,12 @@ static void pl011_set_mctrl(struct uart_port *port, unsigned int mctrl) cr = readw(uap->port.membase + UART011_CR); #define TIOCMBIT(tiocmbit, uartbit) \ - if (mctrl & tiocmbit) \ - cr |= uartbit; \ - else \ - cr &= ~uartbit + do {\ + if (mctrl & tiocmbit) \ + cr |= uartbit; \ + else \ + cr &= ~uartbit; \ + } while (0) TIOCMBIT(TIOCM_RTS, UART011_CR_RTS); TIOCMBIT(TIOCM_DTR, UART011_CR_DTR); @@ -1368,9 +1660,9 @@ static int pl011_startup(struct uart_port *port) int retval; /* - * Try to enable the clock producer. + * Try to enable the clock producer and the regulator. */ - retval = clk_enable(uap->clk); + retval = pl011_power_startup(uap); if (retval) goto out; @@ -1383,29 +1675,7 @@ static int pl011_startup(struct uart_port *port) if (retval) goto clk_dis; - writew(uap->vendor->ifls, uap->port.membase + UART011_IFLS); - - /* - * Provoke TX FIFO interrupt into asserting. - */ - cr = UART01x_CR_UARTEN | UART011_CR_TXE | UART011_CR_LBE; - writew(cr, uap->port.membase + UART011_CR); - writew(0, uap->port.membase + UART011_FBRD); - writew(1, uap->port.membase + UART011_IBRD); - writew(0, uap->port.membase + uap->lcrh_rx); - if (uap->lcrh_tx != uap->lcrh_rx) { - int i; - /* - * Wait 10 PCLKs before writing LCRH_TX register, - * to get this delay write read only register 10 times - */ - for (i = 0; i < 10; ++i) - writew(0xff, uap->port.membase + UART011_MIS); - writew(0, uap->port.membase + uap->lcrh_tx); - } - writew(0, uap->port.membase + UART01x_DR); - while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY) - barrier(); + __pl011_startup(uap); cr = UART01x_CR_UARTEN | UART011_CR_RXE | UART011_CR_TXE; writew(cr, uap->port.membase + UART011_CR); @@ -1417,7 +1687,8 @@ static int pl011_startup(struct uart_port *port) /* * initialise the old status of the modem signals */ - uap->old_status = readw(uap->port.membase + UART01x_FR) & UART01x_FR_MODEM_ANY; + uap->old_status = readw(uap->port.membase + UART01x_FR) & + UART01x_FR_MODEM_ANY; /* Startup DMA */ pl011_dma_startup(uap); @@ -1445,7 +1716,7 @@ static int pl011_startup(struct uart_port *port) return 0; clk_dis: - clk_disable(uap->clk); + pl011_power_shutdown(uap); out: return retval; } @@ -1453,11 +1724,11 @@ static int pl011_startup(struct uart_port *port) static void pl011_shutdown_channel(struct uart_amba_port *uap, unsigned int lcrh) { - unsigned long val; + unsigned long val; - val = readw(uap->port.membase + lcrh); - val &= ~(UART01x_LCRH_BRK | UART01x_LCRH_FEN); - writew(val, uap->port.membase + lcrh); + val = readw(uap->port.membase + lcrh); + val &= ~(UART01x_LCRH_BRK | UART01x_LCRH_FEN); + writew(val, uap->port.membase + lcrh); } static void pl011_shutdown(struct uart_port *port) @@ -1484,7 +1755,8 @@ static void pl011_shutdown(struct uart_port *port) * disable the port */ uap->autorts = false; - writew(UART01x_CR_UARTEN | UART011_CR_TXE, uap->port.membase + UART011_CR); + writew(UART01x_CR_UARTEN | UART011_CR_TXE, + uap->port.membase + UART011_CR); /* * disable break condition and fifos @@ -1493,10 +1765,19 @@ static void pl011_shutdown(struct uart_port *port) if (uap->lcrh_rx != uap->lcrh_tx) pl011_shutdown_channel(uap, uap->lcrh_tx); + if (uap->port.dev->platform_data) { + struct amba_pl011_data *plat; + + plat = uap->port.dev->platform_data; + if (plat->exit) + plat->exit(); + } + /* - * Shut down the clock producer + * Shut down the clock producer and the producer */ - clk_disable(uap->clk); + pl011_power_shutdown(uap); + if (uap->port.dev->platform_data) { struct amba_pl011_data *plat; @@ -1508,6 +1789,32 @@ static void pl011_shutdown(struct uart_port *port) } +/* Power/Clock management. */ +static void pl011_serial_pm(struct uart_port *port, unsigned int state, +unsigned int oldstate) +{ + struct uart_amba_port *uap = (struct uart_amba_port *)port; + + switch (state) { + case 0: /*fully on */ + /* + * Enable the peripheral clock for this serial port. + * This is called on uart_open() or a resume event. + */ + pl011_power_startup(uap); + break; + case 3: /* powered down */ + /* + * Disable the peripheral clock for this serial port. + * This is called on uart_close() or a suspend event. + */ + pl011_power_shutdown(uap); + break; + default: + printk(KERN_ERR "pl011_serial: unknown pm %d\n", state); + } +} + static void pl011_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) @@ -1521,7 +1828,12 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, clkdiv = 8; else clkdiv = 16; - + /* + * Must be before uart_get_baud_rate() call, because + * this function changes baudrate to default in case of 0 + * B0 hangup !!! + */ + pl011_clock_control(port, termios, old); /* * Ask the core to calculate the divisor for us. */ @@ -1543,7 +1855,7 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, case CS7: lcr_h = UART01x_LCRH_WLEN_7; break; - default: // CS8 + default: /* CS8 */ lcr_h = UART01x_LCRH_WLEN_8; break; } @@ -1709,14 +2021,13 @@ static struct uart_ops amba_pl011_pops = { .request_port = pl010_request_port, .config_port = pl010_config_port, .verify_port = pl010_verify_port, + .pm = pl011_serial_pm, #ifdef CONFIG_CONSOLE_POLL .poll_get_char = pl010_get_poll_char, .poll_put_char = pl010_put_poll_char, #endif }; -static struct uart_amba_port *amba_ports[UART_NR]; - #ifdef CONFIG_SERIAL_AMBA_PL011_CONSOLE static void pl011_console_putchar(struct uart_port *port, int ch) @@ -1855,7 +2166,6 @@ static struct uart_driver amba_reg = { .nr = UART_NR, .cons = AMBA_CONSOLE, }; - static int pl011_probe(struct amba_device *dev, const struct amba_id *id) { struct uart_amba_port *uap; @@ -1884,6 +2194,12 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) goto free; } + uap->regulator = regulator_get(&dev->dev, "v-uart"); + if (IS_ERR(uap->regulator)) { + dev_warn(&dev->dev, "could not get uart regulator\n"); + uap->regulator = NULL; + } + uap->clk = clk_get(&dev->dev, NULL); if (IS_ERR(uap->clk)) { ret = PTR_ERR(uap->clk); @@ -1891,6 +2207,7 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) } uap->vendor = vendor; + uap->ifls = vendor->ifls; uap->lcrh_rx = vendor->lcrh_rx; uap->lcrh_tx = vendor->lcrh_tx; uap->fifosize = vendor->fifosize; @@ -1911,6 +2228,9 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) amba_ports[i] = uap; amba_set_drvdata(dev, uap); + + pl011_clock_control_init(uap); + ret = uart_add_one_port(&amba_reg, &uap->port); if (ret) { amba_set_drvdata(dev, NULL); @@ -1918,6 +2238,8 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) pl011_dma_remove(uap); clk_put(uap->clk); unmap: + if (uap->regulator) + regulator_put(uap->regulator); iounmap(base); free: kfree(uap); @@ -1941,6 +2263,8 @@ static int pl011_remove(struct amba_device *dev) pl011_dma_remove(uap); iounmap(uap->port.membase); + if (uap->regulator) + regulator_put(uap->regulator); clk_put(uap->clk); kfree(uap); return 0; @@ -1953,7 +2277,12 @@ static int pl011_suspend(struct amba_device *dev, pm_message_t state) if (!uap) return -EINVAL; +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL + cancel_delayed_work_sync(&uap->clk_off_work); + if (uap->clk_state == PL011_CLK_OFF) + return 0; +#endif return uart_suspend_port(&amba_reg, &uap->port); } @@ -1963,6 +2292,10 @@ static int pl011_resume(struct amba_device *dev) if (!uap) return -EINVAL; +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL + if (uap->clk_state == PL011_CLK_OFF) + return 0; +#endif return uart_resume_port(&amba_reg, &uap->port); } @@ -2019,7 +2352,7 @@ static void __exit pl011_exit(void) * While this can be a module, if builtin it's most likely the console * So let's leave module_exit but move module_init to an earlier place */ -arch_initcall(pl011_init); +subsys_initcall(pl011_init); module_exit(pl011_exit); MODULE_AUTHOR("ARM Ltd/Deep Blue Solutions Ltd"); diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 86b0735e6aa..b4bf8b9880b 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -348,6 +348,22 @@ config IMX2_WDT To compile this driver as a module, choose M here: the module will be called imx2_wdt. +config U8500_WATCHDOG + bool "ST-Ericsson U8500 watchdog" + depends on UX500_SOC_DB8500 + default y + help + Say Y here to include Watchdog timer support for the + watchdog existing in the prcmu of ST-Ericsson U8500 series platforms. + This watchdog is used to reset the system and thus cannot be + compiled as a module. + +config U8500_WATCHDOG_DEBUG + bool "ST-Ericsson U8500 watchdog DEBUG" + depends on UX500_SOC_DB8500 && DEBUG_FS + help + Say Y here to add various debugfs entries in wdog/ + # AVR32 Architecture config AT32AP700X_WDT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 55bd5740e91..4f48616e15b 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o obj-$(CONFIG_ADX_WATCHDOG) += adx_wdt.o obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o +obj-$(CONFIG_U8500_WATCHDOG) += u8500_wdt.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o diff --git a/drivers/watchdog/mpcore_wdt.c b/drivers/watchdog/mpcore_wdt.c index 4dc31024d26..85997b6c298 100644 --- a/drivers/watchdog/mpcore_wdt.c +++ b/drivers/watchdog/mpcore_wdt.c @@ -32,11 +32,13 @@ #include <linux/uaccess.h> #include <linux/slab.h> #include <linux/io.h> +#include <linux/cpufreq.h> +#include <linux/kexec.h> #include <asm/smp_twd.h> struct mpcore_wdt { - unsigned long timer_alive; + cpumask_t timer_alive; struct device *dev; void __iomem *base; int irq; @@ -47,6 +49,8 @@ struct mpcore_wdt { static struct platform_device *mpcore_wdt_dev; static DEFINE_SPINLOCK(wdt_lock); +static DEFINE_PER_CPU(unsigned long, mpcore_wdt_rate); + #define TIMER_MARGIN 60 static int mpcore_margin = TIMER_MARGIN; module_param(mpcore_margin, int, 0); @@ -67,6 +71,8 @@ MODULE_PARM_DESC(mpcore_noboot, "MPcore watchdog action, " "set to 1 to ignore reboots, 0 to reboot (default=" __MODULE_STRING(ONLY_TESTING) ")"); +#define MPCORE_WDT_PERIPHCLK_PRESCALER 2 + /* * This is the interrupt handler. Note that we only use this * in testing mode, so don't actually do a reboot here. @@ -99,9 +105,8 @@ static void mpcore_wdt_keepalive(struct mpcore_wdt *wdt) spin_lock(&wdt_lock); /* Assume prescale is set to 256 */ - count = __raw_readl(wdt->base + TWD_WDOG_COUNTER); - count = (0xFFFFFFFFU - count) * (HZ / 5); - count = (count / 256) * mpcore_margin; + count = per_cpu(mpcore_wdt_rate, smp_processor_id()) / 256; + count = count*mpcore_margin; /* Reload the counter */ writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD); @@ -109,6 +114,56 @@ static void mpcore_wdt_keepalive(struct mpcore_wdt *wdt) spin_unlock(&wdt_lock); } +static void mpcore_wdt_set_rate(unsigned long new_rate) +{ + unsigned long count; + unsigned long long rate_tmp; + unsigned long old_rate; + + spin_lock(&wdt_lock); + old_rate = per_cpu(mpcore_wdt_rate, smp_processor_id()); + per_cpu(mpcore_wdt_rate, smp_processor_id()) = new_rate; + + if (mpcore_wdt_dev) { + struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev); + count = readl(wdt->base + TWD_WDOG_COUNTER); + /* The goal: count = count * (new_rate/old_rate); */ + rate_tmp = (unsigned long long)count * new_rate; + do_div(rate_tmp, old_rate); + count = rate_tmp; + writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD); + wdt->perturb = wdt->perturb ? 0 : 1; + } + spin_unlock(&wdt_lock); +} + +static void mpcore_wdt_update_cpu_frequency_on_cpu(void *data) +{ + struct cpufreq_freqs *freq = data; + mpcore_wdt_set_rate((freq->new * 1000) / + MPCORE_WDT_PERIPHCLK_PRESCALER); +} + +static int mpcore_wdt_cpufreq_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct cpufreq_freqs *freq = data; + + if (event == CPUFREQ_RESUMECHANGE || + (event == CPUFREQ_PRECHANGE && freq->new > freq->old) || + (event == CPUFREQ_POSTCHANGE && freq->new < freq->old)) + smp_call_function_single(freq->cpu, + mpcore_wdt_update_cpu_frequency_on_cpu, + freq, 1); + + return 0; +} + +static struct notifier_block mpcore_wdt_cpufreq_notifier_block = { + .notifier_call = mpcore_wdt_cpufreq_notifier, +}; + + static void mpcore_wdt_stop(struct mpcore_wdt *wdt) { spin_lock(&wdt_lock); @@ -143,6 +198,20 @@ static int mpcore_wdt_set_heartbeat(int t) return 0; } +static int mpcore_wdt_stop_notifier(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev); + printk(KERN_INFO "Stopping watchdog on non-crashing core %u\n", + smp_processor_id()); + mpcore_wdt_stop(wdt); + return NOTIFY_STOP; +} + +static struct notifier_block mpcore_wdt_stop_block = { + .notifier_call = mpcore_wdt_stop_notifier, +}; + /* * /dev/watchdog handling */ @@ -150,7 +219,7 @@ static int mpcore_wdt_open(struct inode *inode, struct file *file) { struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev); - if (test_and_set_bit(0, &wdt->timer_alive)) + if (cpumask_test_and_set_cpu(smp_processor_id(), &wdt->timer_alive)) return -EBUSY; if (nowayout) @@ -158,6 +227,9 @@ static int mpcore_wdt_open(struct inode *inode, struct file *file) file->private_data = wdt; + atomic_notifier_chain_register(&crash_percpu_notifier_list, + &mpcore_wdt_stop_block); + /* * Activate timer */ @@ -181,7 +253,7 @@ static int mpcore_wdt_release(struct inode *inode, struct file *file) "unexpected close, not stopping watchdog!\n"); mpcore_wdt_keepalive(wdt); } - clear_bit(0, &wdt->timer_alive); + cpumask_clear_cpu(smp_processor_id(), &wdt->timer_alive); wdt->expect_close = 0; return 0; } @@ -448,16 +520,31 @@ static char banner[] __initdata = KERN_INFO "MPcore Watchdog Timer: 0.1. " static int __init mpcore_wdt_init(void) { + int i; + /* * Check that the margin value is within it's range; * if not reset to the default */ if (mpcore_wdt_set_heartbeat(mpcore_margin)) { mpcore_wdt_set_heartbeat(TIMER_MARGIN); - printk(KERN_INFO "mpcore_margin value must be 0 < mpcore_margin < 65536, using %d\n", + printk(KERN_INFO "mpcore_wdt: mpcore_margin value must be 0 < mpcore_margin < 65536, using %d\n", TIMER_MARGIN); } + cpufreq_register_notifier(&mpcore_wdt_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + + for_each_online_cpu(i) + per_cpu(mpcore_wdt_rate, i) = + (cpufreq_get(i) * 1000) / MPCORE_WDT_PERIPHCLK_PRESCALER; + + for_each_online_cpu(i) + printk(KERN_INFO + "mpcore_wdt: rate for core %d is %lu.%02luMHz.\n", i, + per_cpu(mpcore_wdt_rate, i) / 1000000, + (per_cpu(mpcore_wdt_rate, i) / 10000) % 100); + printk(banner, mpcore_noboot, mpcore_margin, nowayout); return platform_driver_register(&mpcore_wdt_driver); diff --git a/drivers/watchdog/u8500_wdt.c b/drivers/watchdog/u8500_wdt.c new file mode 100644 index 00000000000..3ac23ab05f3 --- /dev/null +++ b/drivers/watchdog/u8500_wdt.c @@ -0,0 +1,433 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson + * + * Heavily based upon geodewdt.c + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/moduleparam.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/err.h> +#include <linux/uaccess.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/platform_device.h> +#include <linux/mfd/dbx500-prcmu.h> + +#define WATCHDOG_TIMEOUT 600 /* 10 minutes */ + +#define WDT_FLAGS_OPEN 1 +#define WDT_FLAGS_ORPHAN 2 + +static unsigned long wdt_flags; + +static int timeout = WATCHDOG_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) "."); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +static u8 wdog_id; +static bool wdt_en; +static bool wdt_auto_off = false; +static bool safe_close; + +static int u8500_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(WDT_FLAGS_OPEN, &wdt_flags)) + return -EBUSY; + + if (!test_and_clear_bit(WDT_FLAGS_ORPHAN, &wdt_flags)) + __module_get(THIS_MODULE); + + prcmu_enable_a9wdog(wdog_id); + wdt_en = true; + + return nonseekable_open(inode, file); +} + +static int u8500_wdt_release(struct inode *inode, struct file *file) +{ + if (safe_close) { + prcmu_disable_a9wdog(wdog_id); + module_put(THIS_MODULE); + } else { + pr_crit("Unexpected close - watchdog is not stopping.\n"); + prcmu_kick_a9wdog(wdog_id); + + set_bit(WDT_FLAGS_ORPHAN, &wdt_flags); + } + + clear_bit(WDT_FLAGS_OPEN, &wdt_flags); + safe_close = false; + return 0; +} + +static ssize_t u8500_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + if (!len) + return len; + + if (!nowayout) { + size_t i; + safe_close = false; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + + if (c == 'V') + safe_close = true; + } + } + + prcmu_kick_a9wdog(wdog_id); + + return len; +} + +static long u8500_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int interval; + + static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "U8500 WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, + sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_SETOPTIONS: + { + int options; + int ret = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + prcmu_disable_a9wdog(wdog_id); + wdt_en = false; + ret = 0; + } + + if (options & WDIOS_ENABLECARD) { + prcmu_enable_a9wdog(wdog_id); + wdt_en = true; + ret = 0; + } + + return ret; + } + case WDIOC_KEEPALIVE: + return prcmu_kick_a9wdog(wdog_id); + + case WDIOC_SETTIMEOUT: + if (get_user(interval, p)) + return -EFAULT; + + /* 28 bit resolution in ms, becomes 268435.455 s */ + if (interval > 268435 || interval < 0) + return -EINVAL; + timeout = interval; + prcmu_disable_a9wdog(wdog_id); + prcmu_load_a9wdog(wdog_id, timeout * 1000); + prcmu_enable_a9wdog(wdog_id); + + /* Fall through */ + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + + default: + return -ENOTTY; + } + + return 0; +} + +static const struct file_operations u8500_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = u8500_wdt_write, + .unlocked_ioctl = u8500_wdt_ioctl, + .open = u8500_wdt_open, + .release = u8500_wdt_release, +}; + +static struct miscdevice u8500_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &u8500_wdt_fops, +}; + +#ifdef CONFIG_U8500_WATCHDOG_DEBUG + +enum wdog_dbg { + WDOG_DBG_CONFIG, + WDOG_DBG_LOAD, + WDOG_DBG_KICK, + WDOG_DBG_EN, + WDOG_DBG_DIS, +}; + +static ssize_t wdog_dbg_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + unsigned long val; + int err; + enum wdog_dbg v = (enum wdog_dbg)((struct seq_file *) + (file->private_data))->private; + + switch(v) { + case WDOG_DBG_CONFIG: + err = kstrtoul_from_user(user_buf, count, 0, &val); + + if (!err) { + wdt_auto_off = val != 0; + (void) prcmu_config_a9wdog(1, + wdt_auto_off); + } + else { + pr_err("u8500_wdt:dbg: unknown value\n"); + } + break; + case WDOG_DBG_LOAD: + err = kstrtoul_from_user(user_buf, count, 0, &val); + + if (!err) { + timeout = val; + /* Convert seconds to ms */ + prcmu_disable_a9wdog(wdog_id); + prcmu_load_a9wdog(wdog_id, timeout * 1000); + prcmu_enable_a9wdog(wdog_id); + } + else { + pr_err("u8500_wdt:dbg: unknown value\n"); + } + break; + case WDOG_DBG_KICK: + (void) prcmu_kick_a9wdog(wdog_id); + break; + case WDOG_DBG_EN: + wdt_en = true; + (void) prcmu_enable_a9wdog(wdog_id); + break; + case WDOG_DBG_DIS: + wdt_en = false; + (void) prcmu_disable_a9wdog(wdog_id); + break; + } + + return count; +} + +static int wdog_dbg_read(struct seq_file *s, void *p) +{ + enum wdog_dbg v = (enum wdog_dbg)s->private; + + switch(v) { + case WDOG_DBG_CONFIG: + seq_printf(s,"wdog is on id %d, auto off on sleep: %s\n", + (int)wdog_id, + wdt_auto_off ? "enabled": "disabled"); + break; + case WDOG_DBG_LOAD: + /* In 1s */ + seq_printf(s, "wdog load is: %d s\n", + timeout); + break; + case WDOG_DBG_KICK: + break; + case WDOG_DBG_EN: + case WDOG_DBG_DIS: + seq_printf(s, "wdog is %sabled\n", + wdt_en ? "en" : "dis"); + break; + } + return 0; +} + +static int wdog_dbg_open(struct inode *inode, + struct file *file) +{ + return single_open(file, wdog_dbg_read, inode->i_private); +} + +static const struct file_operations wdog_dbg_fops = { + .open = wdog_dbg_open, + .write = wdog_dbg_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int __init wdog_dbg_init(void) +{ + struct dentry *wdog_dir; + + wdog_dir = debugfs_create_dir("wdog", NULL); + if (IS_ERR_OR_NULL(wdog_dir)) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_u8("id", + S_IWUGO | S_IRUGO, wdog_dir, + &wdog_id))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("config", + S_IWUGO | S_IRUGO, wdog_dir, + (void *)WDOG_DBG_CONFIG, + &wdog_dbg_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("load", + S_IWUGO | S_IRUGO, wdog_dir, + (void *)WDOG_DBG_LOAD, + &wdog_dbg_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("kick", + S_IWUGO, wdog_dir, + (void *)WDOG_DBG_KICK, + &wdog_dbg_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("enable", + S_IWUGO | S_IRUGO, wdog_dir, + (void *)WDOG_DBG_EN, + &wdog_dbg_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("disable", + S_IWUGO | S_IRUGO, wdog_dir, + (void *)WDOG_DBG_DIS, + &wdog_dbg_fops))) + goto fail; + + return 0; +fail: + pr_err("u8500:wdog: Failed to initialize wdog dbg\n"); + debugfs_remove_recursive(wdog_dir); + + return -EFAULT; +} + +#else +static inline int __init wdog_dbg_init(void) +{ + return 0; +} +#endif + +static int __init u8500_wdt_probe(struct platform_device *pdev) +{ + int ret; + + /* Number of watch dogs */ + prcmu_config_a9wdog(1, wdt_auto_off); + /* convert to ms */ + prcmu_load_a9wdog(wdog_id, timeout * 1000); + + ret = misc_register(&u8500_wdt_miscdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register misc\n"); + return ret; + } + + ret = wdog_dbg_init(); + if (ret < 0) + goto fail; + + dev_info(&pdev->dev, "initialized\n"); + + return 0; +fail: + misc_deregister(&u8500_wdt_miscdev); + return ret; +} + +static int __exit u8500_wdt_remove(struct platform_device *dev) +{ + prcmu_disable_a9wdog(wdog_id); + wdt_en = false; + misc_deregister(&u8500_wdt_miscdev); + return 0; +} +#ifdef CONFIG_PM +static int u8500_wdt_suspend(struct platform_device *pdev, + pm_message_t state) +{ + if (wdt_en && !wdt_auto_off) { + prcmu_disable_a9wdog(wdog_id); + prcmu_config_a9wdog(1, true); + + prcmu_load_a9wdog(wdog_id, timeout * 1000); + prcmu_enable_a9wdog(wdog_id); + } + return 0; +} + +static int u8500_wdt_resume(struct platform_device *pdev) +{ + if (wdt_en && !wdt_auto_off) { + prcmu_disable_a9wdog(wdog_id); + prcmu_config_a9wdog(1, wdt_auto_off); + + prcmu_load_a9wdog(wdog_id, timeout * 1000); + prcmu_enable_a9wdog(wdog_id); + } + return 0; +} + +#else +#define u8500_wdt_suspend NULL +#define u8500_wdt_resume NULL +#endif +static struct platform_driver u8500_wdt_driver = { + .remove = __exit_p(u8500_wdt_remove), + .driver = { + .owner = THIS_MODULE, + .name = "u8500_wdt", + }, + .suspend = u8500_wdt_suspend, + .resume = u8500_wdt_resume, +}; + +static int __init u8500_wdt_init(void) +{ + return platform_driver_probe(&u8500_wdt_driver, u8500_wdt_probe); +} +module_init(u8500_wdt_init); + +MODULE_AUTHOR("Jonas Aaberg <jonas.aberg@stericsson.com>"); +MODULE_DESCRIPTION("U8500 Watchdog Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/fs/mpage.c b/fs/mpage.c index fdfae9fa98c..cc782f9e68d 100644 --- a/fs/mpage.c +++ b/fs/mpage.c @@ -53,6 +53,8 @@ static void mpage_end_io(struct bio *bio, int err) prefetchw(&bvec->bv_page->flags); if (bio_data_dir(bio) == READ) { if (uptodate) { + /* FIXME: fix to solve cache coherence issues. */ + flush_dcache_page(page); SetPageUptodate(page); } else { ClearPageUptodate(page); diff --git a/fs/partitions/Kconfig b/fs/partitions/Kconfig index cb5f0a3f1b0..097be1934ee 100644 --- a/fs/partitions/Kconfig +++ b/fs/partitions/Kconfig @@ -68,6 +68,25 @@ config ACORN_PARTITION_RISCIX of machines called RISCiX. If you say 'Y' here, Linux will be able to read disks partitioned under RISCiX. +config BLKDEV_PARTITION + bool "Blockdev commandline partition support" if PARTITION_ADVANCED + default n + help + Say Y if you like to setup partitions for block devices by reading + from the kernel command line (kernel boot arguments). + + The format of the partitions on the command line: + blkdevparts=<blkdev-def>[;<blkdev-def>] + <blkdev-def> := <blkdev-id>:<partdef>[,<partdef>] + <partdef> := <size>[@<offset>] + + <blkdev-id> := unique id used to map driver to blockdev name + <size> := size in numbers of sectors + <offset> := offset in sectors for partition to start at + + Example: + blkdevparts=mmc0:1024@0,524288@1024;mmc1:8192@0,8192@8192 + config OSF_PARTITION bool "Alpha OSF partition support" if PARTITION_ADVANCED default y if ALPHA diff --git a/fs/partitions/Makefile b/fs/partitions/Makefile index 03af8eac51d..48b216c53db 100644 --- a/fs/partitions/Makefile +++ b/fs/partitions/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_BLOCK) := check.o obj-$(CONFIG_ACORN_PARTITION) += acorn.o obj-$(CONFIG_AMIGA_PARTITION) += amiga.o obj-$(CONFIG_ATARI_PARTITION) += atari.o +obj-$(CONFIG_BLKDEV_PARTITION) += blkdev_parts.o obj-$(CONFIG_MAC_PARTITION) += mac.o obj-$(CONFIG_LDM_PARTITION) += ldm.o obj-$(CONFIG_MSDOS_PARTITION) += msdos.o diff --git a/fs/partitions/blkdev_parts.c b/fs/partitions/blkdev_parts.c new file mode 100755 index 00000000000..030565b7ce7 --- /dev/null +++ b/fs/partitions/blkdev_parts.c @@ -0,0 +1,127 @@ +/* + * + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ulf Hansson <ulf.hansson@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * + * Create partitions for block devices by reading from the kernel + * command line (kernel boot arguments). + * + */ + +#include "check.h" +#include "blkdev_parts.h" + +static char *cmdline; + +/* + * This is the handler for our kernel commandline parameter, + * called from main.c::checksetup(). + * Note that we can not yet kmalloc() anything, so we only save + * the commandline for later processing. + */ +static int cmdline_setup(char *s) +{ + cmdline = s; + return 1; +} +__setup("blkdevparts=", cmdline_setup); + +/* Parse for a matching blkdev-id and return pointer to partdef */ +static char *parse_blkdev_id(char *blkdev_name) +{ + int blkdev_id_len; + char *p, *blkdev_id; + + /* Start parsing for a matching blkdev-id */ + p = blkdev_id = cmdline; + while (blkdev_id != NULL) { + + /* Find the end of the blkdev-id string */ + p = strchr(blkdev_id, ':'); + if (p == NULL) + return NULL; + + /* Check if we found a matching blkdev-id */ + blkdev_id_len = p - blkdev_id; + if (strlen(blkdev_name) == blkdev_id_len) { + if (strncmp(blkdev_name, blkdev_id, blkdev_id_len) == 0) + return p; + } + + /* Move to next blkdev-id string if there is one */ + blkdev_id = strchr(p, ';'); + if (blkdev_id != NULL) + blkdev_id++; + } + return NULL; +} + +static int parse_partdef(char **part, struct parsed_partitions *state, int part_nbr) +{ + sector_t size, offset; + char *p = *part; + + /* Skip the beginning "," or ":" */ + p++; + + /* Fetch and verify size from partdef */ + size = simple_strtoull(p, &p, 10); + if ((size == 0) || (*p != '@')) + return 0; + + /* Skip the "@" */ + p++; + + /* Fetch offset from partdef and check if there are more parts */ + offset = simple_strtoull(p, &p, 10); + if (*p == ',') + *part = p; + else + *part = NULL; + + /* Add partition to state */ + put_partition(state, part_nbr, offset, size); + printk(KERN_INFO "\nPartition: size=%llu, offset=%llu\n", + (unsigned long long) size, + (unsigned long long) offset); + return 1; +} + +static int parse_blkdev_parts(char *blkdev_name, struct parsed_partitions *state) +{ + char *partdef; + int part_nbr = 0; + + /* Find partdef */ + partdef = parse_blkdev_id(blkdev_name); + + /* Add parts */ + while (partdef != NULL) { + /* Find next part and add it to state */ + part_nbr++; + if (!parse_partdef(&partdef, state, part_nbr)) + return 0; + } + return part_nbr; +} + +int blkdev_partition(struct parsed_partitions *state) +{ + char blkdev_name[BDEVNAME_SIZE]; + + /* Check if there are any partitions to handle */ + if (cmdline == NULL) + return 0; + + /* Get the name of the blockdevice we are operating upon */ + if (bdevname(state->bdev, blkdev_name) == NULL) { + printk(KERN_WARNING "Could not get a blkdev name\n"); + return 0; + } + + /* Parse for partitions and add them to the state */ + return parse_blkdev_parts(blkdev_name, state); +} + diff --git a/fs/partitions/blkdev_parts.h b/fs/partitions/blkdev_parts.h new file mode 100755 index 00000000000..16d2b571625 --- /dev/null +++ b/fs/partitions/blkdev_parts.h @@ -0,0 +1,14 @@ +/* + * + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ulf Hansson <ulf.hansson@stericsson.com> for ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * + * Create partitions for block devices by reading from the kernel + * command line (kernel boot arguments). + * + */ + +int blkdev_partition(struct parsed_partitions *state); + diff --git a/fs/partitions/check.c b/fs/partitions/check.c index e3c63d1c5e1..c8dc879e26b 100644 --- a/fs/partitions/check.c +++ b/fs/partitions/check.c @@ -27,6 +27,7 @@ #include "acorn.h" #include "amiga.h" #include "atari.h" +#include "blkdev_parts.h" #include "ldm.h" #include "mac.h" #include "msdos.h" @@ -50,6 +51,9 @@ static int (*check_part[])(struct parsed_partitions *) = { * Probe partition formats with tables at disk address 0 * that also have an ADFS boot block at 0xdc0. */ +#ifdef CONFIG_BLKDEV_PARTITION + blkdev_partition, +#endif #ifdef CONFIG_ACORN_PARTITION_ICS adfspart_check_ICS, #endif diff --git a/include/linux/amba/bus.h b/include/linux/amba/bus.h index fcbbe71a3cc..849aec9aba5 100644 --- a/include/linux/amba/bus.h +++ b/include/linux/amba/bus.h @@ -94,4 +94,64 @@ void amba_release_regions(struct amba_device *); #define amba_manf(d) AMBA_MANF_BITS((d)->periphid) #define amba_part(d) AMBA_PART_BITS((d)->periphid) +#ifdef CONFIG_PM_SLEEP +extern int amba_pm_prepare(struct device *dev); +extern void amba_pm_complete(struct device *dev); +#else +#define amba_pm_prepare NULL +#define amba_pm_complete NULL +#endif + +#ifdef CONFIG_SUSPEND +extern int amba_pm_suspend(struct device *dev); +extern int amba_pm_suspend_noirq(struct device *dev); +extern int amba_pm_resume(struct device *dev); +extern int amba_pm_resume_noirq(struct device *dev); +#else +#define amba_pm_suspend NULL +#define amba_pm_resume NULL +#define amba_pm_suspend_noirq NULL +#define amba_pm_resume_noirq NULL +#endif + +#ifdef CONFIG_HIBERNATE_CALLBACKS +extern int amba_pm_freeze(struct device *dev); +extern int amba_pm_freeze_noirq(struct device *dev); +extern int amba_pm_thaw(struct device *dev); +extern int amba_pm_thaw_noirq(struct device *dev); +extern int amba_pm_poweroff(struct device *dev); +extern int amba_pm_poweroff_noirq(struct device *dev); +extern int amba_pm_restore(struct device *dev); +extern int amba_pm_restore_noirq(struct device *dev); +#else +#define amba_pm_freeze NULL +#define amba_pm_thaw NULL +#define amba_pm_poweroff NULL +#define amba_pm_restore NULL +#define amba_pm_freeze_noirq NULL +#define amba_pm_thaw_noirq NULL +#define amba_pm_poweroff_noirq NULL +#define amba_pm_restore_noirq NULL +#endif + +#ifdef CONFIG_PM_SLEEP +#define USE_AMBA_PM_SLEEP_OPS \ + .prepare = amba_pm_prepare, \ + .complete = amba_pm_complete, \ + .suspend = amba_pm_suspend, \ + .resume = amba_pm_resume, \ + .freeze = amba_pm_freeze, \ + .thaw = amba_pm_thaw, \ + .poweroff = amba_pm_poweroff, \ + .restore = amba_pm_restore, \ + .suspend_noirq = amba_pm_suspend_noirq, \ + .resume_noirq = amba_pm_resume_noirq, \ + .freeze_noirq = amba_pm_freeze_noirq, \ + .thaw_noirq = amba_pm_thaw_noirq, \ + .poweroff_noirq = amba_pm_poweroff_noirq, \ + .restore_noirq = amba_pm_restore_noirq, +#else +#define USE_AMBA_PM_SLEEP_OPS +#endif + #endif diff --git a/include/linux/amba/mmci.h b/include/linux/amba/mmci.h index 21114810c7c..23536c95fb6 100644 --- a/include/linux/amba/mmci.h +++ b/include/linux/amba/mmci.h @@ -6,6 +6,20 @@ #include <linux/mmc/host.h> +/* + * These defines is places here due to access is needed from machine + * configuration files. + * + * The ST Micro version does not have ROD and reuse the voltage registers + * for direction settings. + */ +#define MCI_ST_DATA2DIREN (1 << 2) +#define MCI_ST_CMDDIREN (1 << 3) +#define MCI_ST_DATA0DIREN (1 << 4) +#define MCI_ST_DATA31DIREN (1 << 5) +#define MCI_ST_FBCLKEN (1 << 7) +#define MCI_ST_DATA74DIREN (1 << 8) + /* Just some dummy forwarding */ struct dma_chan; @@ -18,7 +32,8 @@ struct dma_chan; * @ocr_mask: available voltages on the 4 pins from the block, this * is ignored if a regulator is used, see the MMC_VDD_* masks in * mmc/host.h - * @vdd_handler: a callback function to translate a MMC_VDD_* + * @ios_handler: a callback function to act on specfic ios changes, + * used for example to control a levelshifter * mask into a value to be binary (or set some other custom bits * in MMCIPWR) or:ed and written into the MMCIPWR register of the * block. May also control external power based on the power_mode. @@ -30,6 +45,8 @@ struct dma_chan; * @cd_invert: true if the gpio_cd pin value is active low * @capabilities: the capabilities of the block as implemented in * this platform, signify anything MMC_CAP_* from mmc/host.h + * @sigdir: a bit field indicating for what bits in the MMC bus the host + * should enable signal direction indication. * @dma_filter: function used to select an appropriate RX and TX * DMA channel to be used for DMA, if and only if you're deploying the * generic DMA engine @@ -45,13 +62,13 @@ struct dma_chan; struct mmci_platform_data { unsigned int f_max; unsigned int ocr_mask; - u32 (*vdd_handler)(struct device *, unsigned int vdd, - unsigned char power_mode); + int (*ios_handler)(struct device *, struct mmc_ios *); unsigned int (*status)(struct device *); int gpio_wp; int gpio_cd; bool cd_invert; unsigned long capabilities; + unsigned int sigdir; bool (*dma_filter)(struct dma_chan *chan, void *filter_param); void *dma_rx_param; void *dma_tx_param; diff --git a/include/linux/clksrc-dbx500-prcmu.h b/include/linux/clksrc-dbx500-prcmu.h new file mode 100644 index 00000000000..d1e95042408 --- /dev/null +++ b/include/linux/clksrc-dbx500-prcmu.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Author: Mattias Wallin <mattias.wallin@stericsson.com> + * + */ +#ifndef __CLKSRC_DBX500_PRCMU_H +#define __CLKSRC_DBX500_PRCMU_H + +#include <linux/init.h> +#include <linux/io.h> + +extern void __iomem *clksrc_dbx500_timer_base; + +#ifdef CONFIG_CLKSRC_DBX500_PRCMU +void __init clksrc_dbx500_prcmu_init(void); +#else +void __init clksrc_dbx500_prcmu_init(void) {} +#endif + +#endif diff --git a/include/linux/cpufreq-dbx500.h b/include/linux/cpufreq-dbx500.h new file mode 100644 index 00000000000..80d67083e11 --- /dev/null +++ b/include/linux/cpufreq-dbx500.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + */ +#ifndef __CPUFREQ_DBX500_H +#define __CPUFREQ_DBX500_H + +#include <linux/cpufreq.h> + +int dbx500_cpufreq_get_limits(int cpu, int r, + unsigned int *min, unsigned int *max); + +#endif diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index 6216115c778..1f8e97c2a08 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -199,6 +199,7 @@ extern int __cpufreq_driver_getavg(struct cpufreq_policy *policy, int cpufreq_register_governor(struct cpufreq_governor *governor); void cpufreq_unregister_governor(struct cpufreq_governor *governor); +int cpufreq_update_freq(int cpu, unsigned int min, unsigned int max); /********************************************************************* * CPUFREQ DRIVER INTERFACE * @@ -336,6 +337,7 @@ static inline unsigned int cpufreq_quick_get_max(unsigned int cpu) } #endif +int cpufreq_update_freq(int cpu, unsigned int min, unsigned int max); /********************************************************************* * CPUFREQ DEFAULT GOVERNOR * diff --git a/arch/arm/plat-nomadik/include/plat/gpio.h b/include/linux/gpio/nomadik.h index d5d7e651269..6b094d5e404 100644 --- a/arch/arm/plat-nomadik/include/plat/gpio.h +++ b/include/linux/gpio/nomadik.h @@ -9,20 +9,8 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ -#ifndef __ASM_PLAT_GPIO_H -#define __ASM_PLAT_GPIO_H - -#include <asm-generic/gpio.h> - -/* - * These currently cause a function call to happen, they may be optimized - * if needed by adding cpu-specific defines to identify blocks - * (see mach-pxa/include/mach/gpio.h as an example using GPLR etc) - */ -#define gpio_get_value __gpio_get_value -#define gpio_set_value __gpio_set_value -#define gpio_cansleep __gpio_cansleep -#define gpio_to_irq __gpio_to_irq +#ifndef __GPIO_NOMADIK_H +#define __GPIO_NOMADIK_H /* * "nmk_gpio" and "NMK_GPIO" stand for "Nomadik GPIO", leaving @@ -40,6 +28,7 @@ #define NMK_GPIO_SLPC 0x1c #define NMK_GPIO_AFSLA 0x20 #define NMK_GPIO_AFSLB 0x24 +#define NMK_GPIO_LOWEMI 0x28 #define NMK_GPIO_RIMSC 0x40 #define NMK_GPIO_FIMSC 0x44 @@ -78,6 +67,9 @@ extern int nmk_gpio_get_mode(int gpio); extern void nmk_gpio_wakeups_suspend(void); extern void nmk_gpio_wakeups_resume(void); +extern void nmk_gpio_clocks_enable(void); +extern void nmk_gpio_clocks_disable(void); + extern void nmk_gpio_read_pull(int gpio_bank, u32 *pull_up); /* @@ -93,4 +85,4 @@ struct nmk_gpio_platform_data { bool supports_sleepmode; }; -#endif /* __ASM_PLAT_GPIO_H */ +#endif diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 6b6ee702b00..8e891b5a777 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -15,11 +15,16 @@ #define _HWMON_H_ #include <linux/device.h> +#include <linux/notifier.h> struct device *hwmon_device_register(struct device *dev); void hwmon_device_unregister(struct device *dev); +int hwmon_notifier_register(struct notifier_block *nb); +int hwmon_notifier_unregister(struct notifier_block *nb); +void hwmon_notify(unsigned long val, void *v); + /* Scale user input to sensible values */ static inline int SENSORS_LIMIT(long value, long low, long high) { diff --git a/include/linux/kexec.h b/include/linux/kexec.h index c2478a342cd..f36d95a8223 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -110,6 +110,7 @@ struct kimage { /* kexec interface functions */ extern void machine_kexec(struct kimage *image); +extern void machine_crash_swreset(void); extern int machine_kexec_prepare(struct kimage *image); extern void machine_kexec_cleanup(struct kimage *image); extern asmlinkage long sys_kexec_load(unsigned long entry, @@ -158,6 +159,7 @@ unsigned long paddr_vmcoreinfo_note(void); extern struct kimage *kexec_image; extern struct kimage *kexec_crash_image; +extern struct atomic_notifier_head crash_percpu_notifier_list; #ifndef kexec_flush_icache_page #define kexec_flush_icache_page(page) diff --git a/include/linux/leds-ab5500.h b/include/linux/leds-ab5500.h new file mode 100644 index 00000000000..9ba9ac61d90 --- /dev/null +++ b/include/linux/leds-ab5500.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2011 ST-Ericsson SA. + * + * License Terms: GNU General Public License v2 + * + * Simple driver for HVLED in ST-Ericsson AB5500 Analog baseband Controller + * + * Author: Shreshtha Kumar SAHU <shreshthakumar.sahu@stericsson.com> + */ + +#define AB5500_HVLED0 0 +#define AB5500_HVLED1 1 +#define AB5500_HVLED2 2 +#define AB5500_HVLEDS_MAX 3 + +enum ab5500_fade_delay { + AB5500_FADE_DELAY_BYPASS = 0, + AB5500_FADE_DELAY_HALFSEC, + AB5500_FADE_DELAY_ONESEC, + AB5500_FADE_DELAY_TWOSEC +}; + +struct ab5500_led_conf { + char *name; + u8 led_id; + u8 max_current; + u8 fade_hi; + u8 fade_lo; + bool led_on; +}; + +struct ab5500_hvleds_platform_data { + bool hw_fade; + struct ab5500_led_conf leds[AB5500_HVLEDS_MAX]; +}; diff --git a/include/linux/leds_pwm.h b/include/linux/leds_pwm.h index 33a07116748..9c5eab6e086 100644 --- a/include/linux/leds_pwm.h +++ b/include/linux/leds_pwm.h @@ -11,6 +11,7 @@ struct led_pwm { u8 active_low; unsigned max_brightness; unsigned pwm_period_ns; + unsigned int lth_brightness; }; struct led_pwm_platform_data { diff --git a/include/linux/mfd/ab8500.h b/include/linux/mfd/ab8500.h index 1588d446b4d..424bdc81f68 100644 --- a/include/linux/mfd/ab8500.h +++ b/include/linux/mfd/ab8500.h @@ -157,7 +157,6 @@ struct ab8500 { struct device *dev; struct mutex lock; struct mutex irq_lock; - int irq_base; int irq; u8 chip_id; @@ -172,26 +171,42 @@ struct ab8500 { u8 oldmask[AB8500_NUM_IRQ_REGS]; }; -struct regulator_reg_init; -struct regulator_init_data; +struct ab8500_regulator_platform_data; +struct ab8500_accdet_platform_data; +struct ab8500_denc_platform_data; +struct ab8500_audio_platform_data; struct ab8500_gpio_platform_data; /** * struct ab8500_platform_data - AB8500 platform data + * @pm_power_off: Should machine pm power off hook be registered or not + * @thermal_power_off_pending: Set if there was a thermal alarm + * @thermal_set_time_sec: Time of the thermal alarm + * @thermal_time_out: Time out before the thermal alarm should be ignored * @irq_base: start of AB8500 IRQs, AB8500_NR_IRQS will be used * @init: board-specific initialization after detection of ab8500 - * @num_regulator_reg_init: number of regulator init registers - * @regulator_reg_init: regulator init registers - * @num_regulator: number of regulators * @regulator: machine-specific constraints for regulators + * @accdet: machine-specific Accessory detection data + * @battery: machine-specific battery management data + * @charger: machine-specific charger data + * @btemp: machine-specific battery temp data */ struct ab8500_platform_data { int irq_base; + bool pm_power_off; + bool thermal_power_off_pending; + long thermal_set_time_sec; + long thermal_time_out; void (*init) (struct ab8500 *); - int num_regulator_reg_init; - struct ab8500_regulator_reg_init *regulator_reg_init; - int num_regulator; - struct regulator_init_data *regulator; + struct ab8500_regulator_platform_data *regulator; + struct abx500_accdet_platform_data *accdet; + struct ab8500_bm_data *battery; + struct ab8500_denc_platform_data *denc; + struct ab8500_audio_platform_data *audio; + struct ab8500_charger_platform_data *charger; + struct ab8500_btemp_platform_data *btemp; + struct ab8500_fg_platform_data *fg; + struct ab8500_chargalg_platform_data *chargalg; struct ab8500_gpio_platform_data *gpio; struct abx500_usbgpio_platform_data *usb; }; diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h new file mode 100644 index 00000000000..1fb67d25deb --- /dev/null +++ b/include/linux/mfd/ab8500/bm.h @@ -0,0 +1,514 @@ +/* + * Copyright ST-Ericsson 2009. + * + * Author: Arun Murthy <arun.murthy@stericsson.com> + * Licensed under GPLv2. + */ + +#ifndef _AB8500_BM_H +#define _AB8500_BM_H + +#include <linux/kernel.h> + +/* + * System control 2 register offsets. + * bank = 0x02 + */ +#define AB8500_MAIN_WDOG_CTRL_REG 0x01 +#define AB8500_LOW_BAT_REG 0x03 + +/* + * USB/ULPI register offsets + * Bank : 0x5 + */ +#define AB8500_USB_LINE_STAT_REG 0x80 + +/* + * Charger / status register offfsets + * Bank : 0x0B + */ +#define AB8500_CH_STATUS1_REG 0x00 +#define AB8500_CH_STATUS2_REG 0x01 +#define AB8500_CH_USBCH_STAT1_REG 0x02 +#define AB8500_CH_USBCH_STAT2_REG 0x03 +#define AB8500_CH_FSM_STAT_REG 0x04 +#define AB8500_CH_STAT_REG 0x05 + +/* + * Charger / control register offfsets + * Bank : 0x0B + */ +#define AB8500_CH_VOLT_LVL_REG 0x40 +#define AB8500_CH_VOLT_LVL_MAX_REG 0x41 /*Only in Cut2.0*/ +#define AB8500_CH_OPT_CRNTLVL_REG 0x42 +#define AB8500_CH_OPT_CRNTLVL_MAX_REG 0x43 /*Only in Cut2.0*/ +#define AB8500_CH_WD_TIMER_REG 0x50 +#define AB8500_CHARG_WD_CTRL 0x51 +#define AB8500_BTEMP_HIGH_TH 0x52 +#define AB8500_LED_INDICATOR_PWM_CTRL 0x53 +#define AB8500_LED_INDICATOR_PWM_DUTY 0x54 +#define AB8500_BATT_OVV 0x55 +#define AB8500_CHARGER_CTRL 0x56 +#define AB8500_BAT_CTRL_CURRENT_SOURCE 0x60 /*Only in Cut2.0*/ + +/* + * Charger / main control register offsets + * Bank : 0x0B + */ +#define AB8500_MCH_CTRL1 0x80 +#define AB8500_MCH_CTRL2 0x81 +#define AB8500_MCH_IPT_CURLVL_REG 0x82 +#define AB8500_CH_WD_REG 0x83 + +/* + * Charger / USB control register offsets + * Bank : 0x0B + */ +#define AB8500_USBCH_CTRL1_REG 0xC0 +#define AB8500_USBCH_CTRL2_REG 0xC1 +#define AB8500_USBCH_IPT_CRNTLVL_REG 0xC2 + +/* + * Gas Gauge register offsets + * Bank : 0x0C + */ +#define AB8500_GASG_CC_CTRL_REG 0x00 +#define AB8500_GASG_CC_ACCU1_REG 0x01 +#define AB8500_GASG_CC_ACCU2_REG 0x02 +#define AB8500_GASG_CC_ACCU3_REG 0x03 +#define AB8500_GASG_CC_ACCU4_REG 0x04 +#define AB8500_GASG_CC_SMPL_CNTRL_REG 0x05 +#define AB8500_GASG_CC_SMPL_CNTRH_REG 0x06 +#define AB8500_GASG_CC_SMPL_CNVL_REG 0x07 +#define AB8500_GASG_CC_SMPL_CNVH_REG 0x08 +#define AB8500_GASG_CC_CNTR_AVGOFF_REG 0x09 +#define AB8500_GASG_CC_OFFSET_REG 0x0A +#define AB8500_GASG_CC_NCOV_ACCU 0x10 +#define AB8500_GASG_CC_NCOV_ACCU_CTRL 0x11 +#define AB8500_GASG_CC_NCOV_ACCU_LOW 0x12 +#define AB8500_GASG_CC_NCOV_ACCU_MED 0x13 +#define AB8500_GASG_CC_NCOV_ACCU_HIGH 0x14 + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB8500_IT_SOURCE2_REG 0x01 +#define AB8500_IT_SOURCE21_REG 0x14 + +/* + * RTC register offsets + * Bank: 0x0F + */ +#define AB8500_RTC_BACKUP_CHG_REG 0x0C +#define AB8500_RTC_CC_CONF_REG 0x01 +#define AB8500_RTC_CTRL_REG 0x0B + +/* + * OTP register offsets + * Bank : 0x15 + */ +#define AB8500_OTP_CONF_15 0x0E + +/* GPADC constants from AB8500 spec, UM0836 */ +#define ADC_RESOLUTION 1024 +#define ADC_CH_MAIN_MIN 0 +#define ADC_CH_MAIN_MAX 20030 +#define ADC_CH_VBUS_MIN 0 +#define ADC_CH_VBUS_MAX 20030 +#define ADC_CH_VBAT_MIN 2300 +#define ADC_CH_VBAT_MAX 4800 +#define ADC_CH_BKBAT_MIN 0 +#define ADC_CH_BKBAT_MAX 3200 + +/* Main charge i/p current */ +#define MAIN_CH_IP_CUR_0P9A 0x80 +#define MAIN_CH_IP_CUR_1P0A 0x90 +#define MAIN_CH_IP_CUR_1P1A 0xA0 +#define MAIN_CH_IP_CUR_1P2A 0xB0 +#define MAIN_CH_IP_CUR_1P3A 0xC0 +#define MAIN_CH_IP_CUR_1P4A 0xD0 +#define MAIN_CH_IP_CUR_1P5A 0xE0 + +/* ChVoltLevel */ +#define CH_VOL_LVL_3P5 0x00 +#define CH_VOL_LVL_4P0 0x14 +#define CH_VOL_LVL_4P05 0x16 +#define CH_VOL_LVL_4P1 0x1B +#define CH_VOL_LVL_4P15 0x20 +#define CH_VOL_LVL_4P2 0x25 +#define CH_VOL_LVL_4P6 0x4D + +/* ChOutputCurrentLevel */ +#define CH_OP_CUR_LVL_0P1 0x00 +#define CH_OP_CUR_LVL_0P2 0x01 +#define CH_OP_CUR_LVL_0P3 0x02 +#define CH_OP_CUR_LVL_0P4 0x03 +#define CH_OP_CUR_LVL_0P5 0x04 +#define CH_OP_CUR_LVL_0P6 0x05 +#define CH_OP_CUR_LVL_0P7 0x06 +#define CH_OP_CUR_LVL_0P8 0x07 +#define CH_OP_CUR_LVL_0P9 0x08 +#define CH_OP_CUR_LVL_1P4 0x0D +#define CH_OP_CUR_LVL_1P5 0x0E +#define CH_OP_CUR_LVL_1P6 0x0F + +/* BTEMP High thermal limits */ +#define BTEMP_HIGH_TH_57_0 0x00 +#define BTEMP_HIGH_TH_52 0x01 +#define BTEMP_HIGH_TH_57_1 0x02 +#define BTEMP_HIGH_TH_62 0x03 + +/* current is mA */ +#define USB_0P1A 100 +#define USB_0P2A 200 +#define USB_0P3A 300 +#define USB_0P4A 400 +#define USB_0P5A 500 + +#define LOW_BAT_3P1V 0x20 +#define LOW_BAT_2P3V 0x00 +#define LOW_BAT_RESET 0x01 +#define LOW_BAT_ENABLE 0x01 + +/* Backup battery constants */ +#define BUP_ICH_SEL_50UA 0x00 +#define BUP_ICH_SEL_150UA 0x04 +#define BUP_ICH_SEL_300UA 0x08 +#define BUP_ICH_SEL_700UA 0x0C + +#define BUP_VCH_SEL_2P5V 0x00 +#define BUP_VCH_SEL_2P6V 0x01 +#define BUP_VCH_SEL_2P8V 0x02 +#define BUP_VCH_SEL_3P1V 0x03 + +/* Battery OVV constants */ +#define BATT_OVV_ENA 0x02 +#define BATT_OVV_TH_3P7 0x00 +#define BATT_OVV_TH_4P75 0x01 + +/* VBUS OVV constants */ +#define VBUS_OVV_SELECT_MASK 0x78 +#define VBUS_OVV_SELECT_5P6V 0x00 +#define VBUS_OVV_SELECT_5P7V 0x08 +#define VBUS_OVV_SELECT_5P8V 0x10 +#define VBUS_OVV_SELECT_5P9V 0x18 +#define VBUS_OVV_SELECT_6P0V 0x20 +#define VBUS_OVV_SELECT_6P1V 0x28 +#define VBUS_OVV_SELECT_6P2V 0x30 +#define VBUS_OVV_SELECT_6P3V 0x38 + +#define VBUS_AUTO_IN_CURR_LIM_ENA 0x04 + +/* Fuel Gauge constants */ +#define RESET_ACCU 0x02 +#define READ_REQ 0x01 +#define CC_DEEP_SLEEP_ENA 0x02 +#define CC_PWR_UP_ENA 0x01 +#define CC_SAMPLES_40 0x28 +#define RD_NCONV_ACCU_REQ 0x01 +#define CC_CALIB 0x08 +#define CC_INTAVGOFFSET_ENA 0x10 +#define CC_MUXOFFSET 0x80 +#define CC_INT_CAL_N_AVG_MASK 0x60 +#define CC_INT_CAL_SAMPLES_16 0x40 +#define CC_INT_CAL_SAMPLES_8 0x20 +#define CC_INT_CAL_SAMPLES_4 0x00 + +/* RTC constants */ +#define RTC_BUP_CH_ENA 0x10 + +/* BatCtrl Current Source Constants */ +#define BAT_CTRL_7U_ENA 0x01 +#define BAT_CTRL_20U_ENA 0x02 +#define BAT_CTRL_CMP_ENA 0x04 +#define FORCE_BAT_CTRL_CMP_HIGH 0x08 +#define BAT_CTRL_PULL_UP_ENA 0x10 + +/* Battery type */ +#define BATTERY_UNKNOWN 00 + +/* Concurrent instant current i/f */ +#define INVALID_CURRENT INT_MAX + +/* + * ADC for the battery thermistor. + * When using the ADC_THERM_BATCTRL the battery ID resistor is combined with + * a NTC resistor to both identify the battery and to measure its temperature. + * Different phone manufactures uses different techniques to both identify the + * battery and to read its temperature. + */ +enum adc_therm { + ADC_THERM_BATCTRL, + ADC_THERM_BATTEMP, +}; + +/** + * struct res_to_temp - defines one point in a temp to res curve. To + * be used in battery packs that combines the identification resistor with a + * NTC resistor. + * @temp: battery pack temperature in Celcius + * @resist: NTC resistor net total resistance + */ +struct res_to_temp { + int temp; + int resist; +}; + +/** + * struct v_to_cap - Table for translating voltage to capacity + * @voltage: Voltage in mV + * @capacity: Capacity in percent + */ +struct v_to_cap { + int voltage; + int capacity; +}; + +/* Forward declaration */ +struct ab8500_fg; + +/** + * struct ab8500_fg_parameters - Fuel gauge algorithm parameters, in seconds + * if not specified + * @recovery_sleep_timer: Time between measurements while recovering + * @recovery_total_time: Total recovery time + * @init_timer: Measurement interval during startup + * @init_discard_time: Time we discard voltage measurement at startup + * @init_total_time: Total init time during startup + * @high_curr_time: Time current has to be high to go to recovery + * @accu_charging: FG accumulation time while charging + * @accu_high_curr: FG accumulation time in high current mode + * @high_curr_threshold: High current threshold, in mA + * @lowbat_threshold: Low battery threshold, in mV + */ +struct ab8500_fg_parameters { + int recovery_sleep_timer; + int recovery_total_time; + int init_timer; + int init_discard_time; + int init_total_time; + int high_curr_time; + int accu_charging; + int accu_high_curr; + int high_curr_threshold; + int lowbat_threshold; +}; + +/** + * struct ab8500_charger_maximization - struct used by the board config. + * @use_maxi: Enable maximization for this battery type + * @maxi_chg_curr: Maximum charger current allowed + * @maxi_wait_cycles: cycles to wait before setting charger current + * @charger_curr_step delta between two charger current settings (mA) + */ +struct ab8500_maxim_parameters { + bool ena_maxi; + int chg_curr; + int wait_cycles; + int charger_curr_step; +}; + +/** + * struct battery_type - different batteries supported + * @name: battery technology + * @resis_high: battery upper resistance limit + * @resis_low: battery lower resistance limit + * @charge_full_design: Maximum battery capacity in mAh + * @nominal_voltage: Nominal voltage of the battery in mV + * @termination_vol: max voltage upto which battery can be charged + * @termination_curr battery charging termination current in mA + * @recharge_vol battery voltage limit that will trigger a new + * full charging cycle in the case where maintenan- + * -ce charging has been disabled + * @normal_cur_lvl: charger current in normal state in mA + * @normal_vol_lvl: charger voltage in normal state in mV + * @maint_a_cur_lvl: charger current in maintenance A state in mA + * @maint_a_vol_lvl: charger voltage in maintenance A state in mV + * @maint_a_chg_timer_h: charge time in maintenance A state + * @maint_b_cur_lvl: charger current in maintenance B state in mA + * @maint_b_vol_lvl: charger voltage in maintenance B state in mV + * @maint_b_chg_timer_h: charge time in maintenance B state + * @low_high_cur_lvl: charger current in temp low/high state in mA + * @low_high_vol_lvl: charger voltage in temp low/high state in mV' + * @battery_resistance: battery inner resistance in mOhm. + * @n_r_t_tbl_elements: number of elements in r_to_t_tbl + * @r_to_t_tbl: table containing resistance to temp points + * @n_v_cap_tbl_elements: number of elements in v_to_cap_tbl + * @v_to_cap_tbl: Voltage to capacity (in %) table + */ +struct battery_type { + int name; + int resis_high; + int resis_low; + int charge_full_design; + int nominal_voltage; + int termination_vol; + int termination_curr; + int recharge_vol; + int normal_cur_lvl; + int normal_vol_lvl; + int maint_a_cur_lvl; + int maint_a_vol_lvl; + int maint_a_chg_timer_h; + int maint_b_cur_lvl; + int maint_b_vol_lvl; + int maint_b_chg_timer_h; + int low_high_cur_lvl; + int low_high_vol_lvl; + int battery_resistance; + int n_temp_tbl_elements; + struct res_to_temp *r_to_t_tbl; + int n_v_cap_tbl_elements; + struct v_to_cap *v_to_cap_tbl; +}; + +/** + * struct ab8500_bm_capacity_levels - ab8500 capacity level data + * @critical: critical capacity level in percent + * @low: low capacity level in percent + * @normal: normal capacity level in percent + * @high: high capacity level in percent + * @full: full capacity level in percent + */ +struct ab8500_bm_capacity_levels { + int critical; + int low; + int normal; + int high; + int full; +}; + +/** + * struct ab8500_bm_charger_parameters - Charger specific parameters + * @usb_volt_max: maximum allowed USB charger voltage in mV + * @usb_curr_max: maximum allowed USB charger current in mA + * @ac_volt_max: maximum allowed AC charger voltage in mV + * @ac_curr_max: maximum allowed AC charger current in mA + */ +struct ab8500_bm_charger_parameters { + int usb_volt_max; + int usb_curr_max; + int ac_volt_max; + int ac_curr_max; +}; + +/** + * struct ab8500_bm_data - ab8500 battery management data + * @temp_under under this temp, charging is stopped + * @temp_low between this temp and temp_under charging is reduced + * @temp_high between this temp and temp_over charging is reduced + * @temp_over over this temp, charging is stopped + * @temp_interval_chg temperature measurement interval in s when charging + * @temp_interval_nochg temperature measurement interval in s when not charging + * @main_safety_tmr_h safety timer for main charger + * @usb_safety_tmr_h safety timer for usb charger + * @bkup_bat_v voltage which we charge the backup battery with + * @bkup_bat_i current which we charge the backup battery with + * @no_maintenance indicates that maintenance charging is disabled + * @adc_therm placement of thermistor, batctrl or battemp adc + * @chg_unknown_bat flag to enable charging of unknown batteries + * @enable_overshoot flag to enable VBAT overshoot control + * @fg_res resistance of FG resistor in mOhm + * @n_btypes number of elements in array bat_type + * @batt_id index of the identified battery in array bat_type + * @interval_charging charge alg cycle period time when charging (sec) + * @interval_not_charging charge alg cycle period time when not charging (sec) + * @temp_hysteresis temperature hysteresis + * @gnd_lift_resistance Battery ground to phone ground resistance (mOhm) + * @maxi: maximization parameters + * @cap_levels capacity in percent for the different capacity levels + * @bat_type table of supported battery types + * @chg_params charger parameters + * @fg_params fuel gauge parameters + */ +struct ab8500_bm_data { + int temp_under; + int temp_low; + int temp_high; + int temp_over; + int temp_interval_chg; + int temp_interval_nochg; + int main_safety_tmr_h; + int usb_safety_tmr_h; + int bkup_bat_v; + int bkup_bat_i; + bool no_maintenance; + bool chg_unknown_bat; + bool enable_overshoot; + enum adc_therm adc_therm; + int fg_res; + int n_btypes; + int batt_id; + int interval_charging; + int interval_not_charging; + int temp_hysteresis; + int gnd_lift_resistance; + const struct ab8500_maxim_parameters *maxi; + const struct ab8500_bm_capacity_levels *cap_levels; + const struct battery_type *bat_type; + const struct ab8500_bm_charger_parameters *chg_params; + const struct ab8500_fg_parameters *fg_params; +}; + +struct ab8500_charger_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct ab8500_btemp_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct ab8500_fg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct ab8500_chargalg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; +struct ab8500_btemp; +struct ab8500_gpadc; +struct ab8500_fg; +#ifdef CONFIG_AB8500_BM +void ab8500_fg_reinit(void); +void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA); +struct ab8500_btemp *ab8500_btemp_get(void); +int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp); +struct ab8500_fg *ab8500_fg_get(void); +int ab8500_fg_inst_curr_blocking(struct ab8500_fg *dev); +int ab8500_fg_inst_curr_nonblocking(struct ab8500_fg *dev, int *local_result); +#else +static void ab8500_fg_reinit(void) +{ +} +static void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) +{ +} +static struct ab8500_btemp *ab8500_btemp_get(void) +{ + return NULL; +} +static int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp) +{ + return 0; +} +struct ab8500_fg *ab8500_fg_get(void) +{ + return NULL; +} +static int ab8500_fg_inst_curr_blocking(struct ab8500_fg *dev) +{ + return -ENODEV; +} +static int ab8500_fg_inst_curr_nonblocking( + struct ab8500_fg *dev, + int *local_result) +{ + return -ENODEV; +} +#endif +#endif /* _AB8500_BM_H */ diff --git a/include/linux/mfd/ab8500/denc-regs.h b/include/linux/mfd/ab8500/denc-regs.h new file mode 100644 index 00000000000..a6683ca7470 --- /dev/null +++ b/include/linux/mfd/ab8500/denc-regs.h @@ -0,0 +1,357 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * ST-Ericsson AB8500 DENC related registers + * + * Author: Marcus Tunnissen <marcel.tuennissen@stericsson.com> + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#ifndef __AB8500_DENC_H +#define __AB8500_DENC_H + +#define AB8500_VAL2REG(__reg, __fld, __val) \ + (((__val) << __reg##_##__fld##_SHIFT) & __reg##_##__fld##_MASK) +#define AB8500_REG2VAL(__reg, __fld, __val) \ + (((__val) & __reg##_##__fld##_MASK) >> __reg##_##__fld##_SHIFT) + +#define AB8500_CTRL3 0x00000200 +#define AB8500_CTRL3_TH_SD_ENA_SHIFT 3 +#define AB8500_CTRL3_TH_SD_ENA_MASK 0x00000008 +#define AB8500_CTRL3_TH_SD_ENA(__x) \ + AB8500_VAL2REG(AB8500_CTRL3, TH_SD_ENA, __x) +#define AB8500_CTRL3_RESET_DENC_N_SHIFT 2 +#define AB8500_CTRL3_RESET_DENC_N_MASK 0x00000004 +#define AB8500_CTRL3_RESET_DENC_N(__x) \ + AB8500_VAL2REG(AB8500_CTRL3, RESET_DENC_N, __x) +#define AB8500_CTRL3_RESET_AUD_N_SHIFT 1 +#define AB8500_CTRL3_RESET_AUD_N_MASK 0x00000002 +#define AB8500_CTRL3_RESET_AUD_N(__x) \ + AB8500_VAL2REG(AB8500_CTRL3, RESET_AUD_N, __x) +#define AB8500_CTRL3_CLK_32K_OUT2_IS_SHIFT 0 +#define AB8500_CTRL3_CLK_32K_OUT2_IS_MASK 0x00000001 +#define AB8500_CTRL3_CLK_32K_OUT2_IS(__x) \ + AB8500_VAL2REG(AB8500_CTRL3, CLK_32K_OUT2_IS, __x) +#define AB8500_SYS_ULP_CLK_CONF 0x0000020A +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_PD_ENA_SHIFT 7 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_PD_ENA_MASK 0x00000080 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_PD_ENA(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_PD_ENA, __x) +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_BUF_ENA_SHIFT 6 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_BUF_ENA_MASK 0x00000040 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_BUF_ENA(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_BUF_ENA, __x) +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_STRE_SHIFT 5 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_STRE_MASK 0x00000020 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_STRE(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, ULP_CLK_STRE, __x) +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_INV_SHIFT 4 +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_INV_MASK 0x00000010 +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_INV(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, TVOUT_CLK_INV, __x) +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_DE_IN_SHIFT 3 +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_DE_IN_MASK 0x00000008 +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_DE_IN(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, TVOUT_CLK_DE_IN, __x) +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_STRE_SHIFT 2 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_STRE_MASK 0x00000004 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_STRE(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_STRE, __x) +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_SHIFT 0 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_MASK 0x00000003 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_NO_FUNC 0 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_AS_OUTPUT 1 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_AS_INPUT 2 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, ULP_CLK_CONF, \ + AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_##__x) +#define AB8500_SYS_CLK_CTRL 0x0000020C +#define AB8500_SYS_CLK_CTRL_USB_CLK_VALID_SHIFT 2 +#define AB8500_SYS_CLK_CTRL_USB_CLK_VALID_MASK 0x00000004 +#define AB8500_SYS_CLK_CTRL_USB_CLK_VALID(__x) \ + AB8500_VAL2REG(AB8500_SYS_CLK_CTRL, USB_CLK_VALID, __x) +#define AB8500_SYS_CLK_CTRL_TVOUT_CLK_VALID_SHIFT 1 +#define AB8500_SYS_CLK_CTRL_TVOUT_CLK_VALID_MASK 0x00000002 +#define AB8500_SYS_CLK_CTRL_TVOUT_CLK_VALID(__x) \ + AB8500_VAL2REG(AB8500_SYS_CLK_CTRL, TVOUT_CLK_VALID, __x) +#define AB8500_SYS_CLK_CTRL_TVOUT_PLL_ENA_SHIFT 0 +#define AB8500_SYS_CLK_CTRL_TVOUT_PLL_ENA_MASK 0x00000001 +#define AB8500_SYS_CLK_CTRL_TVOUT_PLL_ENA(__x) \ + AB8500_VAL2REG(AB8500_SYS_CLK_CTRL, TVOUT_PLL_ENA, __x) +#define AB8500_REGU_MISC1 0x00000380 +#define AB8500_REGU_MISC1_V_TVOUT_LP_SHIFT 7 +#define AB8500_REGU_MISC1_V_TVOUT_LP_MASK 0x00000080 +#define AB8500_REGU_MISC1_V_TVOUT_LP(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_TVOUT_LP, __x) +#define AB8500_REGU_MISC1_V_INT_CORE_12_LP_SHIFT 6 +#define AB8500_REGU_MISC1_V_INT_CORE_12_LP_MASK 0x00000040 +#define AB8500_REGU_MISC1_V_INT_CORE_12_LP(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_INT_CORE_12_LP, __x) +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_SHIFT 3 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_MASK 0x00000038 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_2V 0 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_225V 1 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_25V 2 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_275V 3 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_3V 4 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_325V 5 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_35V 6 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_INT_CORE_12_SEL, \ + AB8500_REGU_MISC1_V_INT_CORE_12_SEL_##__x) +#define AB8500_REGU_MISC1_V_INT_CORE_12_ENA_SHIFT 2 +#define AB8500_REGU_MISC1_V_INT_CORE_12_ENA_MASK 0x00000004 +#define AB8500_REGU_MISC1_V_INT_CORE_12_ENA(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_INT_CORE_12_ENA, __x) +#define AB8500_REGU_MISC1_V_TVOUT_ENA_SHIFT 1 +#define AB8500_REGU_MISC1_V_TVOUT_ENA_MASK 0x00000002 +#define AB8500_REGU_MISC1_V_TVOUT_ENA(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_TVOUT_ENA, __x) +#define AB8500_VAUX12_REGU 0x00000409 +#define AB8500_VAUX12_REGU_VAUX_1_SHIFT 2 +#define AB8500_VAUX12_REGU_VAUX_1_MASK 0x0000000C +#define AB8500_VAUX12_REGU_VAUX_1_DISABLE 0 +#define AB8500_VAUX12_REGU_VAUX_1_FORCE_HP 1 +#define AB8500_VAUX12_REGU_VAUX_1_BY_CTRL_REG 2 +#define AB8500_VAUX12_REGU_VAUX_1_FORCE_LP 3 +#define AB8500_VAUX12_REGU_VAUX_1(__x) \ + AB8500_VAL2REG(AB8500_VAUX12_REGU, VAUX_1, \ + AB8500_VAUX12_REGU_VAUX_1_##__x) +#define AB8500_VAUX12_REGU_VAUX_2_SHIFT 0 +#define AB8500_VAUX12_REGU_VAUX_2_MASK 0x00000003 +#define AB8500_VAUX12_REGU_VAUX_2_DISABLE 0 +#define AB8500_VAUX12_REGU_VAUX_2_FORCE_HP 1 +#define AB8500_VAUX12_REGU_VAUX_2_BY_CTRL_REG 2 +#define AB8500_VAUX12_REGU_VAUX_2_FORCE_LP 3 +#define AB8500_VAUX12_REGU_VAUX_2(__x) \ + AB8500_VAL2REG(AB8500_VAUX12_REGU, VAUX_2, \ + AB8500_VAUX12_REGU_VAUX_2_##__x) +#define AB8500_VAUX1_SEL 0x0000041F +#define AB8500_VAUX1_SEL_VAL_SHIFT 0 +#define AB8500_VAUX1_SEL_VAL_MASK 0x0000000F +#define AB8500_VAUX1_SEL_VAL_1_1V 0 +#define AB8500_VAUX1_SEL_VAL_1_2V 1 +#define AB8500_VAUX1_SEL_VAL_1_3V 2 +#define AB8500_VAUX1_SEL_VAL_1_4V 3 +#define AB8500_VAUX1_SEL_VAL_1_5V 4 +#define AB8500_VAUX1_SEL_VAL_1_8V 5 +#define AB8500_VAUX1_SEL_VAL_1_85V 6 +#define AB8500_VAUX1_SEL_VAL_1_9V 7 +#define AB8500_VAUX1_SEL_VAL_2_5V 8 +#define AB8500_VAUX1_SEL_VAL_2_65V 9 +#define AB8500_VAUX1_SEL_VAL_2_7V 10 +#define AB8500_VAUX1_SEL_VAL_2_75V 11 +#define AB8500_VAUX1_SEL_VAL_2_8V 12 +#define AB8500_VAUX1_SEL_VAL_2_9V 13 +#define AB8500_VAUX1_SEL_VAL_3_0V 14 +#define AB8500_VAUX1_SEL_VAL_3_3V 15 +#define AB8500_VAUX1_SEL_VAL(__x) \ + AB8500_VAL2REG(AB8500_VAUX1_SEL, VAL, AB8500_VAUX1_SEL_VAL_##__x) +#define AB8500_DENC_CONF0 0x00000600 +#define AB8500_DENC_CONF0_STD_SHIFT 6 +#define AB8500_DENC_CONF0_STD_MASK 0x000000C0 +#define AB8500_DENC_CONF0_STD_PAL_BDGHI 0 +#define AB8500_DENC_CONF0_STD_PAL_N 1 +#define AB8500_DENC_CONF0_STD_NTSC_M 2 +#define AB8500_DENC_CONF0_STD_PAL_M 3 +#define AB8500_DENC_CONF0_STD(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF0, STD, AB8500_DENC_CONF0_STD_##__x) +#define AB8500_DENC_CONF0_SYNC_SHIFT 3 +#define AB8500_DENC_CONF0_SYNC_MASK 0x00000038 +#define AB8500_DENC_CONF0_SYNC_F_BASED_SLAVE 1 +#define AB8500_DENC_CONF0_SYNC_AUTO_TEST 7 +#define AB8500_DENC_CONF0_SYNC(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF0, SYNC, AB8500_DENC_CONF0_SYNC_##__x) +#define AB8500_DENC_CONF1 0x00000601 +#define AB8500_DENC_CONF1_BLK_LI_SHIFT 7 +#define AB8500_DENC_CONF1_BLK_LI_MASK 0x00000080 +#define AB8500_DENC_CONF1_BLK_LI_PARTIAL 0 +#define AB8500_DENC_CONF1_BLK_LI_FULL 1 +#define AB8500_DENC_CONF1_BLK_LI(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, BLK_LI, \ + AB8500_DENC_CONF1_BLK_LI_##__x) +#define AB8500_DENC_CONF1_FLT_SHIFT 5 +#define AB8500_DENC_CONF1_FLT_MASK 0x00000060 +#define AB8500_DENC_CONF1_FLT_1_1MHZ 0 +#define AB8500_DENC_CONF1_FLT_1_3MHZ 1 +#define AB8500_DENC_CONF1_FLT_1_6MHZ 2 +#define AB8500_DENC_CONF1_FLT_1_9MHZ 3 +#define AB8500_DENC_CONF1_FLT(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, FLT, AB8500_DENC_CONF1_FLT_##__x) +#define AB8500_DENC_CONF1_CO_KI_SHIFT 3 +#define AB8500_DENC_CONF1_CO_KI_MASK 0x00000008 +#define AB8500_DENC_CONF1_CO_KI(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, CO_KI, __x) +#define AB8500_DENC_CONF1_SETUP_MAIN_SHIFT 2 +#define AB8500_DENC_CONF1_SETUP_MAIN_MASK 0x00000004 +#define AB8500_DENC_CONF1_SETUP_MAIN_BLACK_EQ_BLANK 0 +#define AB8500_DENC_CONF1_SETUP_MAIN_BLACK_GT_BLANK 1 +#define AB8500_DENC_CONF1_SETUP_MAIN(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, SETUP_MAIN, \ + AB8500_DENC_CONF1_SETUP_MAIN_##__x) +#define AB8500_DENC_CONF1_CC_SHIFT 0 +#define AB8500_DENC_CONF1_CC_MASK 0x00000003 +#define AB8500_DENC_CONF1_CC_NONE 0 +#define AB8500_DENC_CONF1_CC_FIELD_1 1 +#define AB8500_DENC_CONF1_CC_FIELD_2 2 +#define AB8500_DENC_CONF1_CC_ALL 3 +#define AB8500_DENC_CONF1_CC(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, CC, AB8500_DENC_CONF1_CC_##__x) +#define AB8500_DENC_CONF2 0x00000602 +#define AB8500_DENC_CONF2_N_INTRL_SHIFT 7 +#define AB8500_DENC_CONF2_N_INTRL_MASK 0x00000080 +#define AB8500_DENC_CONF2_N_INTRL(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, N_INTRL, __x) +#define AB8500_DENC_CONF2_EN_RST_SHIFT 6 +#define AB8500_DENC_CONF2_EN_RST_MASK 0x00000040 +#define AB8500_DENC_CONF2_EN_RST(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, EN_RST, __x) +#define AB8500_DENC_CONF2_BURST_EN_SHIFT 5 +#define AB8500_DENC_CONF2_BURST_EN_MASK 0x00000020 +#define AB8500_DENC_CONF2_BURST_EN(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, BURST_EN, __x) +#define AB8500_DENC_CONF2_SEL_RST_SHIFT 4 +#define AB8500_DENC_CONF2_SEL_RST_MASK 0x00000010 +#define AB8500_DENC_CONF2_SEL_RST_USE_HW_VAL 0 +#define AB8500_DENC_CONF2_SEL_RST_USE_PROG_VAL 1 +#define AB8500_DENC_CONF2_SEL_RST(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, SEL_RST, \ + AB8500_DENC_CONF2_SEL_RST_##__x) +#define AB8500_DENC_CONF2_RST_OSC_BUF_SHIFT 2 +#define AB8500_DENC_CONF2_RST_OSC_BUF_MASK 0x00000004 +#define AB8500_DENC_CONF2_RST_OSC_BUF(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, RST_OSC_BUF, __x) +#define AB8500_DENC_CONF2_VAL_RST_SHIFT 0 +#define AB8500_DENC_CONF2_VAL_RST_MASK 0x00000003 +#define AB8500_DENC_CONF2_VAL_RST_ALL_LINES 0 +#define AB8500_DENC_CONF2_VAL_RST_EVERY_2ND_FIELD 1 +#define AB8500_DENC_CONF2_VAL_RST_EVERY_4TH_FIELD 2 +#define AB8500_DENC_CONF2_VAL_RST_EVERY_8TH_FIELD 3 +#define AB8500_DENC_CONF2_VAL_RST(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, VAL_RST, \ + AB8500_DENC_CONF2_VAL_RST_##__x) +#define AB8500_DENC_CONF6 0x00000606 +#define AB8500_DENC_CONF6_SOFT_RESET_SHIFT 7 +#define AB8500_DENC_CONF6_SOFT_RESET_MASK 0x00000080 +#define AB8500_DENC_CONF6_SOFT_RESET(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, SOFT_RESET, __x) +#define AB8500_DENC_CONF6_JUMP_SHIFT 6 +#define AB8500_DENC_CONF6_JUMP_MASK 0x00000040 +#define AB8500_DENC_CONF6_JUMP(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, JUMP, __x) +#define AB8500_DENC_CONF6_DEC_NINC_SHIFT 5 +#define AB8500_DENC_CONF6_DEC_NINC_MASK 0x00000020 +#define AB8500_DENC_CONF6_DEC_NINC(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, DEC_NINC, __x) +#define AB8500_DENC_CONF6_FREE_JUMP_SHIFT 4 +#define AB8500_DENC_CONF6_FREE_JUMP_MASK 0x00000010 +#define AB8500_DENC_CONF6_FREE_JUMP(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, FREE_JUMP, __x) +#define AB8500_DENC_CONF6_MAX_DYN_SHIFT 0 +#define AB8500_DENC_CONF6_MAX_DYN_MASK 0x00000001 +#define AB8500_DENC_CONF6_MAX_DYN(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, MAX_DYN, __x) +#define AB8500_DENC_CONF8 0x00000608 +#define AB8500_DENC_CONF8_PH_RST_MODE_SHIFT 6 +#define AB8500_DENC_CONF8_PH_RST_MODE_MASK 0x000000C0 +#define AB8500_DENC_CONF8_PH_RST_MODE_DISABLED 0 +#define AB8500_DENC_CONF8_PH_RST_MODE_UPDATE_FROM_PHASE_BUF 1 +#define AB8500_DENC_CONF8_PH_RST_MODE_UPDATE_FROM_INC_DFS 2 +#define AB8500_DENC_CONF8_PH_RST_MODE_RESET 3 +#define AB8500_DENC_CONF8_PH_RST_MODE(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF8, PH_RST_MODE, \ + AB8500_DENC_CONF8_PH_RST_MODE_##__x) +#define AB8500_DENC_CONF8_VAL_422_MUX_SHIFT 4 +#define AB8500_DENC_CONF8_VAL_422_MUX_MASK 0x00000010 +#define AB8500_DENC_CONF8_VAL_422_MUX_TEST 0 +#define AB8500_DENC_CONF8_VAL_422_MUX_ACTIVE 1 +#define AB8500_DENC_CONF8_VAL_422_MUX(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF8, VAL_422_MUX, \ + AB8500_DENC_CONF8_VAL_422_MUX_##__x) +#define AB8500_DENC_CONF8_BLK_ALL_SHIFT 3 +#define AB8500_DENC_CONF8_BLK_ALL_MASK 0x00000008 +#define AB8500_DENC_CONF8_BLK_ALL(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF8, BLK_ALL, __x) +#define AB8500_TVOUT_CTRL 0x00000680 +#define AB8500_TVOUT_CTRL_TV_LOAD_RC_SHIFT 6 +#define AB8500_TVOUT_CTRL_TV_LOAD_RC_MASK 0x00000040 +#define AB8500_TVOUT_CTRL_TV_LOAD_RC(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, TV_LOAD_RC, __x) +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_SHIFT 3 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_MASK 0x00000038 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_0_5S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_1S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_1_5S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_2S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_2_5S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_3S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, PLUG_TV_TIME, \ + AB8500_TVOUT_CTRL_PLUG_TV_TIME_##__x) +#define AB8500_TVOUT_CTRL_TV_PLUG_ON_SHIFT 2 +#define AB8500_TVOUT_CTRL_TV_PLUG_ON_MASK 0x00000004 +#define AB8500_TVOUT_CTRL_TV_PLUG_ON(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, TV_PLUG_ON, __x) +#define AB8500_TVOUT_CTRL_DAC_CTRL0_SHIFT 1 +#define AB8500_TVOUT_CTRL_DAC_CTRL0_MASK 0x00000002 +#define AB8500_TVOUT_CTRL_DAC_CTRL0(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, DAC_CTRL0, __x) +#define AB8500_TVOUT_CTRL_DAC_CTRL1_SHIFT 0 +#define AB8500_TVOUT_CTRL_DAC_CTRL1_MASK 0x00000001 +#define AB8500_TVOUT_CTRL_DAC_CTRL1(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, DAC_CTRL1, __x) +#define AB8500_TVOUT_CTRL2 0x00000681 +#define AB8500_TVOUT_CTRL2_SWAP_DDR_DATA_IN_SHIFT 1 +#define AB8500_TVOUT_CTRL2_SWAP_DDR_DATA_IN_MASK 0x00000002 +#define AB8500_TVOUT_CTRL2_SWAP_DDR_DATA_IN(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL2, SWAP_DDR_DATA_IN, __x) +#define AB8500_TVOUT_CTRL2_DENC_DDR_SHIFT 0 +#define AB8500_TVOUT_CTRL2_DENC_DDR_MASK 0x00000001 +#define AB8500_TVOUT_CTRL2_DENC_DDR(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL2, DENC_DDR, __x) +#define AB8500_IT_MASK1 0x00000E40 +#define AB8500_IT_MASK1_PON_KEY1_DBR_SHIFT 7 +#define AB8500_IT_MASK1_PON_KEY1_DBR_MASK 0x00000080 +#define AB8500_IT_MASK1_PON_KEY1_DBR(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PON_KEY1_DBR, __x) +#define AB8500_IT_MASK1_PON_KEY1_DBF_SHIFT 6 +#define AB8500_IT_MASK1_PON_KEY1_DBF_MASK 0x00000040 +#define AB8500_IT_MASK1_PON_KEY1_DBF(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PON_KEY1_DBF, __x) +#define AB8500_IT_MASK1_PON_KEY2_DBR_SHIFT 5 +#define AB8500_IT_MASK1_PON_KEY2_DBR_MASK 0x00000020 +#define AB8500_IT_MASK1_PON_KEY2_DBR(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PON_KEY2_DBR, __x) +#define AB8500_IT_MASK1_PON_KEY2_DBF_SHIFT 4 +#define AB8500_IT_MASK1_PON_KEY2_DBF_MASK 0x00000010 +#define AB8500_IT_MASK1_PON_KEY2_DBF(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PON_KEY2_DBF, __x) +#define AB8500_IT_MASK1_TEMP_WARN_SHIFT 3 +#define AB8500_IT_MASK1_TEMP_WARN_MASK 0x00000008 +#define AB8500_IT_MASK1_TEMP_WARN(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, TEMP_WARN, __x) +#define AB8500_IT_MASK1_PLUG_TV_DET_SHIFT 2 +#define AB8500_IT_MASK1_PLUG_TV_DET_MASK 0x00000004 +#define AB8500_IT_MASK1_PLUG_TV_DET(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PLUG_TV_DET, __x) +#define AB8500_IT_MASK1_UNPLUG_TV_DET_SHIFT 1 +#define AB8500_IT_MASK1_UNPLUG_TV_DET_MASK 0x00000002 +#define AB8500_IT_MASK1_UNPLUG_TV_DET(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, UNPLUG_TV_DET, __x) +#define AB8500_IT_MASK1_MAIN_EXT_CH_NOK_SHIFT 0 +#define AB8500_IT_MASK1_MAIN_EXT_CH_NOK_MASK 0x00000001 +#define AB8500_IT_MASK1_MAIN_EXT_CH_NOK(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, MAIN_EXT_CH_NOK, __x) +#define AB8500_REV 0x00001080 +#define AB8500_REV_FULL_MASK_SHIFT 4 +#define AB8500_REV_FULL_MASK_MASK 0x000000F0 +#define AB8500_REV_FULL_MASK(__x) \ + AB8500_VAL2REG(AB8500_REV, FULL_MASK, __x) +#define AB8500_REV_METAL_FIX_SHIFT 0 +#define AB8500_REV_METAL_FIX_MASK 0x0000000F +#define AB8500_REV_METAL_FIX(__x) \ + AB8500_VAL2REG(AB8500_REV, METAL_FIX, __x) + +#endif /* __AB8500_DENC_H */ diff --git a/include/linux/mfd/ab8500/denc.h b/include/linux/mfd/ab8500/denc.h new file mode 100644 index 00000000000..25a09a2c2bd --- /dev/null +++ b/include/linux/mfd/ab8500/denc.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * AB8500 tvout driver interface + * + * Author: Marcel Tunnissen <marcel.tuennissen@stericsson.com> + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ +#ifndef __AB8500_DENC__H__ +#define __AB8500_DENC__H__ + +#include <linux/platform_device.h> + +struct ab8500_denc_platform_data { + /* Platform info */ + bool ddr_enable; + bool ddr_little_endian; +}; + +enum ab8500_denc_TV_std { + TV_STD_PAL_BDGHI, + TV_STD_PAL_N, + TV_STD_PAL_M, + TV_STD_NTSC_M, +}; + +enum ab8500_denc_cr_filter_bandwidth { + TV_CR_NTSC_LOW_DEF_FILTER, + TV_CR_PAL_LOW_DEF_FILTER, + TV_CR_NTSC_HIGH_DEF_FILTER, + TV_CR_PAL_HIGH_DEF_FILTER, +}; + +enum ab8500_denc_phase_reset_mode { + TV_PHASE_RST_MOD_DISABLE, + TV_PHASE_RST_MOD_FROM_PHASE_BUF, + TV_PHASE_RST_MOD_FROM_INC_DFS, + TV_PHASE_RST_MOD_RST, +}; + +enum ab8500_denc_plug_time { + TV_PLUG_TIME_0_5S, + TV_PLUG_TIME_1S, + TV_PLUG_TIME_1_5S, + TV_PLUG_TIME_2S, + TV_PLUG_TIME_2_5S, + TV_PLUG_TIME_3S, +}; + +struct ab8500_denc_conf { + /* register settings for DENC_configuration */ + bool act_output; + enum ab8500_denc_TV_std TV_std; + bool progressive; + bool test_pattern; + bool partial_blanking; + bool blank_all; + bool black_level_setup; + enum ab8500_denc_cr_filter_bandwidth cr_filter; + bool suppress_col; + enum ab8500_denc_phase_reset_mode phase_reset_mode; + bool dac_enable; + bool act_dc_output; +}; + +struct platform_device *ab8500_denc_get_device(void); +void ab8500_denc_put_device(struct platform_device *pdev); + +void ab8500_denc_reset(struct platform_device *pdev, bool hard); +void ab8500_denc_power_up(struct platform_device *pdev); +void ab8500_denc_power_down(struct platform_device *pdev); + +void ab8500_denc_conf(struct platform_device *pdev, + struct ab8500_denc_conf *conf); +void ab8500_denc_conf_plug_detect(struct platform_device *pdev, + bool enable, bool load_RC, + enum ab8500_denc_plug_time time); +void ab8500_denc_mask_int_plug_det(struct platform_device *pdev, bool plug, + bool unplug); +#endif /* __AB8500_DENC__H__ */ diff --git a/include/linux/mfd/ab8500/gpadc.h b/include/linux/mfd/ab8500/gpadc.h index 46b954011f1..fa706c5a04a 100644 --- a/include/linux/mfd/ab8500/gpadc.h +++ b/include/linux/mfd/ab8500/gpadc.h @@ -26,7 +26,10 @@ struct ab8500_gpadc; -struct ab8500_gpadc *ab8500_gpadc_get(char *name); -int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input); +struct ab8500_gpadc *ab8500_gpadc_get(void); +int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 channel); +int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel); +int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, + u8 channel, int ad_value); #endif /* _AB8500_GPADC_H */ diff --git a/include/linux/mfd/ab8500/gpio.h b/include/linux/mfd/ab8500/gpio.h index 488a8c920a2..d88e3025317 100644 --- a/include/linux/mfd/ab8500/gpio.h +++ b/include/linux/mfd/ab8500/gpio.h @@ -8,6 +8,8 @@ #ifndef _AB8500_GPIO_H #define _AB8500_GPIO_H +#include <mach/gpio.h> + /* * Platform data to register a block: only the initial gpio/irq number. */ @@ -16,6 +18,62 @@ struct ab8500_gpio_platform_data { int gpio_base; u32 irq_base; u8 config_reg[7]; + u8 config_direction[6]; + u8 config_pullups[6]; +}; + +enum ab8500_pin { + AB8500_PIN_GPIO1 = AB8500_GPIO_BASE, + AB8500_PIN_GPIO2, + AB8500_PIN_GPIO3, + AB8500_PIN_GPIO4, + AB8500_PIN_GPIO5, + AB8500_PIN_GPIO6, + AB8500_PIN_GPIO7, + AB8500_PIN_GPIO8, + AB8500_PIN_GPIO9, + AB8500_PIN_GPIO10, + AB8500_PIN_GPIO11, + AB8500_PIN_GPIO12, + AB8500_PIN_GPIO13, + AB8500_PIN_GPIO14, + AB8500_PIN_GPIO15, + AB8500_PIN_GPIO16, + AB8500_PIN_GPIO17, + AB8500_PIN_GPIO18, + AB8500_PIN_GPIO19, + AB8500_PIN_GPIO20, + AB8500_PIN_GPIO21, + AB8500_PIN_GPIO22, + AB8500_PIN_GPIO23, + AB8500_PIN_GPIO24, + AB8500_PIN_GPIO25, + AB8500_PIN_GPIO26, + AB8500_PIN_GPIO27, + AB8500_PIN_GPIO28, + AB8500_PIN_GPIO29, + AB8500_PIN_GPIO30, + AB8500_PIN_GPIO31, + AB8500_PIN_GPIO32, + AB8500_PIN_GPIO33, + AB8500_PIN_GPIO34, + AB8500_PIN_GPIO35, + AB8500_PIN_GPIO36, + AB8500_PIN_GPIO37, + AB8500_PIN_GPIO38, + AB8500_PIN_GPIO39, + AB8500_PIN_GPIO40, + AB8500_PIN_GPIO41, + AB8500_PIN_GPIO42, }; +int ab8500_config_pulldown(struct device *dev, + enum ab8500_pin gpio, bool enable); + +int ab8500_gpio_config_select(struct device *dev, + enum ab8500_pin gpio, bool gpio_select); + +int ab8500_gpio_config_get_select(struct device *dev, + enum ab8500_pin gpio, bool *gpio_select); + #endif /* _AB8500_GPIO_H */ diff --git a/include/linux/mfd/ab8500/ux500_chargalg.h b/include/linux/mfd/ab8500/ux500_chargalg.h new file mode 100644 index 00000000000..f04e47ff56a --- /dev/null +++ b/include/linux/mfd/ab8500/ux500_chargalg.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Johan Gardsmark <johan.gardsmark@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _UX500_CHARGALG_H +#define _UX500_CHARGALG_H + +#include <linux/power_supply.h> + +#define psy_to_ux500_charger(x) container_of((x), \ + struct ux500_charger, psy) + +/* Forward declaration */ +struct ux500_charger; + +struct ux500_charger_ops { + int (*enable) (struct ux500_charger *, int, int, int); + int (*kick_wd) (struct ux500_charger *); + int (*update_curr) (struct ux500_charger *, int); +}; + +/** + * struct ux500_charger - power supply ux500 charger sub class + * @psy power supply base class + * @ops ux500 charger operations + * @max_out_volt maximum output charger voltage in mV + * @max_out_curr maximum output charger current in mA + */ +struct ux500_charger { + struct power_supply psy; + struct ux500_charger_ops ops; + int max_out_volt; + int max_out_curr; +}; + +#endif diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 896b5e47f16..816b3983ac4 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -1,12 +1,13 @@ /* - * Copyright (C) 2007-2009 ST-Ericsson AB - * License terms: GNU General Public License (GPL) version 2 + * Copyright (C) ST-Ericsson SA 2010 + * License terms: GNU General Public License v2 * AB3100 core access functions * Author: Linus Walleij <linus.walleij@stericsson.com> * * ABX500 core access functions. * The abx500 interface is used for the Analog Baseband chip - * ab3100, ab3550, ab5500, and ab8500. + * ab3100, ab3550, ab5500 and possibly comming. It is not used for + * ab4500 and ab8500 since they are another family of chip. * * Author: Mattias Wallin <mattias.wallin@stericsson.com> * Author: Mattias Nilsson <mattias.i.nilsson@stericsson.com> @@ -31,8 +32,8 @@ #define AB3100_R2B 0xc8 #define AB3550_P1A 0x10 #define AB5500_1_0 0x20 -#define AB5500_2_0 0x21 -#define AB5500_2_1 0x22 +#define AB5500_1_1 0x21 +#define AB5500_2_0 0x24 /* AB8500 CIDs*/ #define AB8500_CUTEARLY 0x00 @@ -41,6 +42,13 @@ #define AB8500_CUT2P0 0x20 #define AB8500_CUT3P0 0x30 +/* AB8500 CIDs*/ +#define AB8500_CUT1P0 0x10 +#define AB8500_CUT1P1 0x11 +#define AB8500_CUT2P0 0x20 +#define AB8500_CUT3P0 0x30 +#define AB8500_CUT3P3 0x33 + /* * AB3100, EVENTA1, A2 and A3 event register flags * these are catenated into a single 32-bit flag in the code @@ -91,6 +99,15 @@ */ #define AB3100_NUM_REGULATORS 10 +/* Turn On Events */ +#define POR_ON_VBAT_EVENT (0x01 << 0) +#define P_ON_KEY1_EVENT (0x01 << 1) +#define P_ON_KEY2_EVENT (0x01 << 2) +#define RTC_ALARM_EVENT (0x01 << 3) +#define MAIN_CH_DET_EVENT (0x01 << 4) +#define VBUS_DET_EVENT (0x01 << 5) +#define USB_ID_DET_EVENT (0x01 << 6) + /** * struct ab3100 * @access_mutex: lock out concurrent accesses to the AB3100 registers @@ -224,6 +241,12 @@ int abx500_get_chip_id(struct device *dev); int abx500_event_registers_startup_state_get(struct device *dev, u8 *event); int abx500_startup_irq_enabled(struct device *dev, unsigned int irq); +#define abx500_get abx500_get_register_interruptible +#define abx500_set abx500_set_register_interruptible +#define abx500_get_page abx500_get_register_page_interruptible +#define abx500_set_page abx500_set_register_page_interruptible +#define abx500_mask_and_set abx500_mask_and_set_register_interruptible + struct abx500_ops { int (*get_chip_id) (struct device *); int (*get_register) (struct device *, u8, u8, u8 *); @@ -235,6 +258,250 @@ struct abx500_ops { int (*startup_irq_enabled) (struct device *, unsigned int); }; -int abx500_register_ops(struct device *core_dev, struct abx500_ops *ops); +/* Battery driver related data */ +/* + * ADC for the battery thermistor. + * When using the ABx500_ADC_THERM_BATCTRL the battery ID resistor is combined + * with a NTC resistor to both identify the battery and to measure its + * temperature. Different phone manufactures uses different techniques to both + * identify the battery and to read its temperature. + */ +enum abx500_adc_therm { + ABx500_ADC_THERM_BATCTRL, + ABx500_ADC_THERM_BATTEMP, +}; + +/** + * struct abx500_res_to_temp - defines one point in a temp to res curve. To + * be used in battery packs that combines the identification resistor with a + * NTC resistor. + * @temp: battery pack temperature in Celcius + * @resist: NTC resistor net total resistance + */ +struct abx500_res_to_temp { + int temp; + int resist; +}; + +/** + * struct abx500_v_to_cap - Table for translating voltage to capacity + * @voltage: Voltage in mV + * @capacity: Capacity in percent + */ +struct abx500_v_to_cap { + int voltage; + int capacity; +}; + +/* Forward declaration */ +struct abx500_fg; + +/** + * struct abx500_fg_parameters - Fuel gauge algorithm parameters, in seconds + * if not specified + * @recovery_sleep_timer: Time between measurements while recovering + * @recovery_total_time: Total recovery time + * @init_timer: Measurement interval during startup + * @init_discard_time: Time we discard voltage measurement at startup + * @init_total_time: Total init time during startup + * @high_curr_time: Time current has to be high to go to recovery + * @accu_charging: FG accumulation time while charging + * @accu_high_curr: FG accumulation time in high current mode + * @high_curr_threshold: High current threshold, in mA + * @lowbat_threshold: Low battery threshold, in mV + */ +struct abx500_fg_parameters { + int recovery_sleep_timer; + int recovery_total_time; + int init_timer; + int init_discard_time; + int init_total_time; + int high_curr_time; + int accu_charging; + int accu_high_curr; + int high_curr_threshold; + int lowbat_threshold; +}; + +/** + * struct abx500_charger_maximization - struct used by the board config. + * @use_maxi: Enable maximization for this battery type + * @maxi_chg_curr: Maximum charger current allowed + * @maxi_wait_cycles: cycles to wait before setting charger current + * @charger_curr_step delta between two charger current settings (mA) + */ +struct abx500_maxim_parameters { + bool ena_maxi; + int chg_curr; + int wait_cycles; + int charger_curr_step; +}; + +/** + * struct abx500_battery_type - different batteries supported + * @name: battery technology + * @resis_high: battery upper resistance limit + * @resis_low: battery lower resistance limit + * @charge_full_design: Maximum battery capacity in mAh + * @nominal_voltage: Nominal voltage of the battery in mV + * @termination_vol: max voltage upto which battery can be charged + * @termination_curr battery charging termination current in mA + * @recharge_vol battery voltage limit that will trigger a new + * full charging cycle in the case where maintenan- + * -ce charging has been disabled + * @normal_cur_lvl: charger current in normal state in mA + * @normal_vol_lvl: charger voltage in normal state in mV + * @maint_a_cur_lvl: charger current in maintenance A state in mA + * @maint_a_vol_lvl: charger voltage in maintenance A state in mV + * @maint_a_chg_timer_h: charge time in maintenance A state + * @maint_b_cur_lvl: charger current in maintenance B state in mA + * @maint_b_vol_lvl: charger voltage in maintenance B state in mV + * @maint_b_chg_timer_h: charge time in maintenance B state + * @low_high_cur_lvl: charger current in temp low/high state in mA + * @low_high_vol_lvl: charger voltage in temp low/high state in mV' + * @battery_resistance: battery inner resistance in mOhm. + * @n_r_t_tbl_elements: number of elements in r_to_t_tbl + * @r_to_t_tbl: table containing resistance to temp points + * @n_v_cap_tbl_elements: number of elements in v_to_cap_tbl + * @v_to_cap_tbl: Voltage to capacity (in %) table + */ +struct abx500_battery_type { + int name; + int resis_high; + int resis_low; + int charge_full_design; + int nominal_voltage; + int termination_vol; + int termination_curr; + int recharge_vol; + int normal_cur_lvl; + int normal_vol_lvl; + int maint_a_cur_lvl; + int maint_a_vol_lvl; + int maint_a_chg_timer_h; + int maint_b_cur_lvl; + int maint_b_vol_lvl; + int maint_b_chg_timer_h; + int low_high_cur_lvl; + int low_high_vol_lvl; + int battery_resistance; + int n_temp_tbl_elements; + struct abx500_res_to_temp *r_to_t_tbl; + int n_v_cap_tbl_elements; + struct abx500_v_to_cap *v_to_cap_tbl; +}; + +/** + * struct abx500_bm_capacity_levels - abx500 capacity level data + * @critical: critical capacity level in percent + * @low: low capacity level in percent + * @normal: normal capacity level in percent + * @high: high capacity level in percent + * @full: full capacity level in percent + */ +struct abx500_bm_capacity_levels { + int critical; + int low; + int normal; + int high; + int full; +}; + +/** + * struct abx500_bm_charger_parameters - Charger specific parameters + * @usb_volt_max: maximum allowed USB charger voltage in mV + * @usb_curr_max: maximum allowed USB charger current in mA + * @ac_volt_max: maximum allowed AC charger voltage in mV + * @ac_curr_max: maximum allowed AC charger current in mA + */ +struct abx500_bm_charger_parameters { + int usb_volt_max; + int usb_curr_max; + int ac_volt_max; + int ac_curr_max; +}; + +/** + * struct abx500_bm_data - abx500 battery management data + * @temp_under under this temp, charging is stopped + * @temp_low between this temp and temp_under charging is reduced + * @temp_high between this temp and temp_over charging is reduced + * @temp_over over this temp, charging is stopped + * @main_safety_tmr_h safety timer for main charger + * @usb_safety_tmr_h safety timer for usb charger + * @bkup_bat_v voltage which we charge the backup battery with + * @bkup_bat_i current which we charge the backup battery with + * @no_maintenance indicates that maintenance charging is disabled + * @abx500_adc_therm placement of thermistor, batctrl or battemp adc + * @chg_unknown_bat flag to enable charging of unknown batteries + * @enable_overshoot flag to enable VBAT overshoot control + * @fg_res resistance of FG resistor in mOhm + * @n_btypes number of elements in array bat_type + * @batt_id index of the identified battery in array bat_type + * @interval_charging charge alg cycle period time when charging (sec) + * @interval_not_charging charge alg cycle period time when not charging (sec) + * @temp_hysteresis temperature hysteresis + * @maxi: maximization parameters + * @cap_levels capacity in percent for the different capacity levels + * @bat_type table of supported battery types + * @chg_params charger parameters + * @fg_params fuel gauge parameters + */ +struct abx500_bm_data { + int temp_under; + int temp_low; + int temp_high; + int temp_over; + int temp_now; + int main_safety_tmr_h; + int usb_safety_tmr_h; + int bkup_bat_v; + int bkup_bat_i; + bool no_maintenance; + bool chg_unknown_bat; + bool enable_overshoot; + enum abx500_adc_therm adc_therm; + int fg_res; + int n_btypes; + int batt_id; + int interval_charging; + int interval_not_charging; + int temp_hysteresis; + const struct abx500_maxim_parameters *maxi; + const struct abx500_bm_capacity_levels *cap_levels; + const struct abx500_battery_type *bat_type; + const struct abx500_bm_charger_parameters *chg_params; + const struct abx500_fg_parameters *fg_params; +}; + +struct abx500_chargalg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct abx500_charger_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct abx500_btemp_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct abx500_fg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct abx500_bm_plat_data { + struct abx500_bm_data *battery; + struct abx500_charger_platform_data *charger; + struct abx500_btemp_platform_data *btemp; + struct abx500_fg_platform_data *fg; + struct abx500_chargalg_platform_data *chargalg; +}; + +int abx500_register_ops(struct device *dev, struct abx500_ops *ops); void abx500_remove_ops(struct device *dev); #endif diff --git a/include/linux/mfd/abx500/ab5500-bm.h b/include/linux/mfd/abx500/ab5500-bm.h new file mode 100644 index 00000000000..cfad11c530c --- /dev/null +++ b/include/linux/mfd/abx500/ab5500-bm.h @@ -0,0 +1,114 @@ +/* + * Copyright ST-Ericsson 2011. + * + * Author: Arun Murthy <arun.murthy@stericsson.com> + * Licensed under GPLv2. + */ + +#ifndef _AB5500_BM_H +#define _AB5500_BM_H + +#define AB5500_MCB 0x2F +/* + * USB/ULPI register offsets + * Bank : 0x5 + */ +#define AB5500_USB_LINE_STATUS 0x80 +#define AB5500_USB_PHY_STATUS 0x89 +#define AB5500_CHGFSM_CHARGER_DETECT 0xBF +#define AB5500_CHGFSM_USB_BTEMP_CURR_LIM 0xAD +#define AB5500_USB_LINE_CTRL2 0x82 +#define AB5500_USB_OTG_CTRL 0x87 + +/* + * Charger / control register offfsets + * Bank : 0x0B + */ +#define AB5500_CVBUSM 0x11 +#define AB5500_LEDT 0x12 +#define AB5500_VSRC 0x13 +#define AB5500_ICSR 0x14 +#define AB5500_OCSRV 0x15 +#define AB5500_CVREC 0x16 +#define AB5500_CREVS 0x17 +#define AB5500_CCTRL 0x18 +#define AB5500_TBDATA 0x19 +#define AB5500_CPWM 0x1A +#define AB5500_DCIOCURRENT 0x1B +#define AB5500_USB_HS_CURR_LIM 0x1C +#define AB5500_WALL_HS_CURR_LIM 0x1D + +/* + * FG, Battcom and ACC registers offsets + * Bank : 0x0C + */ +#define AB5500_FG_CH0 0x00 +#define AB5500_FG_CH1 0x01 +#define AB5500_FG_CH2 0x02 +#define AB5500_FG_DIS_CH0 0x03 +#define AB5500_FG_DIS_CH1 0x04 +#define AB5500_FG_DIS_CH2 0x05 +#define AB5500_FGDIS_COUNT0 0x06 +#define AB5500_FGDIS_COUNT1 0x07 +#define AB5500_FG_VAL_COUNT0 0x08 +#define AB5500_FG_VAL_COUNT1 0x09 +#define AB5500_FGDIR_READ0 0x0A +#define AB5500_FGDIR_READ1 0x0B +#define AB5500_FG_CONTROL_A 0x0C +#define AB5500_FG_CONTROL_B 0x0F +#define AB5500_FG_CONTROL_C 0x10 +#define AB5500_FG_DIS 0x0D +#define AB5500_FG_EOC 0x0E +#define AB5500_FG_CB 0x0F +#define AB5500_FG_CC 0x10 +#define AB5500_UIOR 0x1A +#define AB5500_UART 0x1B +#define AB5500_URI 0x1C +#define AB5500_UART_RQ 0x1D +#define AB5500_ACC_DETECT1 0x20 +#define AB5500_ACC_DETECT2 0x21 +#define AB5500_ACC_DETECTCTRL 0x23 +#define AB5500_ACC_AVCTRL 0x24 +#define AB5500_ACC_DETECT3_DEG_LITCH_TIME 0x30 +#define AB5500_ACC_DETECT3_KEY_PRESS_TIME 0x31 +#define AB5500_ACC_DETECT3_LONG_KEY_TIME 0x32 +#define AB5500_ACC_DETECT3_TIME_READ_MS 0x33 +#define AB5500_ACC_DETECT3_TIME_READ_LS 0x34 +#define AB5500_ACC_DETECT3_CONTROL 0x35 +#define AB5500_ACC_DETECT3_LEVEL 0x36 +#define AB5500_ACC_DETECT3_TIMER_READ_CTL 0x37 + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB5500_IT_SOURCE8 0x28 +#define AB5500_IT_SOURCE9 0x29 + +/* BatCtrl Current Source Constants */ +#define BAT_CTRL_7U_ENA (0x01 << 0) +#define BAT_CTRL_15U_ENA (0x01 << 1) +#define BAT_CTRL_30U_ENA (0x01 << 2) +#define BAT_CTRL_60U_ENA (0x01 << 3) +#define BAT_CTRL_120U_ENA (0x01 << 4) +#define BAT_CTRL_CMP_ENA 0x04 +#define FORCE_BAT_CTRL_CMP_HIGH 0x08 +#define BAT_CTRL_PULL_UP_ENA 0x10 + +/* Battery type */ +#define BATTERY_UNKNOWN 0 + +#ifdef CONFIG_AB5500_BM +struct ab5500_btemp *ab5500_btemp_get(void); +int ab5500_btemp_get_batctrl_temp(struct ab5500_btemp *btemp); +#else +static inline struct ab5500_btemp *ab5500_btemp_get(void) +{ + return 0; +} +static inline int ab5500_btemp_get_batctrl_temp(struct ab5500_btemp *btemp) +{ + return 0; +} +#endif +#endif /* _AB5500_BM_H */ diff --git a/include/linux/mfd/abx500/ab5500-gpadc.h b/include/linux/mfd/abx500/ab5500-gpadc.h new file mode 100644 index 00000000000..3282b44114e --- /dev/null +++ b/include/linux/mfd/abx500/ab5500-gpadc.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 ST-Ericsson SA + * Licensed under GPLv2. + * + * Author: Vijaya Kumar K <vijay.kilari@stericsson.com> + */ + +#ifndef _AB5500_GPADC_H +#define _AB5500_GPADC_H + +/* + * GPADC source: + * The BTEMP_BALL and PCB_TEMP are same. They differ if the + * battery supports internal NTC resistor connected to BDATA + * line. In this case, the BTEMP_BALL correspondss to BDATA + * of GPADC as per AB5500 product spec. + */ + +#define BTEMP_BALL 0 +#define ACC_DETECT2 1 +#define ACC_DETECT3 2 +#define MAIN_BAT_V 3 +#define MAIN_BAT_V_TXON 4 +#define VBUS_V 5 +#define USB_CHARGER_C 6 +#define BK_BAT_V 7 +#define DIE_TEMP 8 +#define PCB_TEMP 9 +#define XTAL_TEMP 10 +#define USB_ID 11 +#define BAT_CTRL 12 +/* VBAT with TXON only min trigger */ +#define MAIN_BAT_V_TXON_TRIG_MIN 13 +/* VBAT with TX off only min trigger */ +#define MAIN_BAT_V_TRIG_MIN 14 + +/* + * Frequency of auto adc conversion + */ +#define MS1000 0x0 +#define MS500 0x1 +#define MS200 0x2 +#define MS100 0x3 +#define MS10 0x4 + +struct ab5500_gpadc; + +/* + * struct adc_auto_input - AB5500 GPADC auto trigger + * @adc_mux Mux input + * @freq freq of conversion + * @min min value for trigger + * @max max value for trigger + * @auto_adc_callback notification callback + */ +struct adc_auto_input { + u8 mux; + u8 freq; + int min; + int max; + int (*auto_adc_callback)(int mux); +}; + +struct ab5500_gpadc *ab5500_gpadc_get(const char *name); +int ab5500_gpadc_convert(struct ab5500_gpadc *gpadc, u8 input); +int ab5500_gpadc_convert_auto(struct ab5500_gpadc *gpadc, + struct adc_auto_input *auto_input); + +#endif /* _AB5500_GPADC_H */ diff --git a/include/linux/mfd/abx500/ab5500.h b/include/linux/mfd/abx500/ab5500.h new file mode 100644 index 00000000000..7c200c483b0 --- /dev/null +++ b/include/linux/mfd/abx500/ab5500.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) ST-Ericsson BLR 2011 + * + * License Terms: GNU General Public License v2 + * Author: Bibek Basu <bibek.basu@stericsson.com> + */ +#ifndef MFD_AB5500_H +#define MFD_AB5500_H + +#include <linux/device.h> + + +enum ab5500_devid { + AB5500_DEVID_ADC, + AB5500_DEVID_LEDS, + AB5500_DEVID_POWER, + AB5500_DEVID_REGULATORS, + AB5500_DEVID_SIM, + AB5500_DEVID_RTC, + AB5500_DEVID_CHARGER, + AB5500_DEVID_FG, + AB5500_DEVID_VIBRATOR, + AB5500_DEVID_CODEC, + AB5500_DEVID_USB, + AB5500_DEVID_OTP, + AB5500_DEVID_VIDEO, + AB5500_DEVID_DBIECI, + AB5500_DEVID_ONSWA, + AB5500_DEVID_CHARGALG, + AB5500_DEVID_BTEMP, + AB5500_DEVID_TEMPMON, + AB5500_DEVID_ACCDET, + AB5500_NUM_DEVICES, +}; + +enum ab5500_banks { + AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP = 0, + AB5500_BANK_VDDDIG_IO_I2C_CLK_TST = 1, + AB5500_BANK_VDENC = 2, + AB5500_BANK_SIM_USBSIM = 3, + AB5500_BANK_LED = 4, + AB5500_BANK_ADC = 5, + AB5500_BANK_RTC = 6, + AB5500_BANK_STARTUP = 7, + AB5500_BANK_DBI_ECI = 8, + AB5500_BANK_CHG = 9, + AB5500_BANK_FG_BATTCOM_ACC = 10, + AB5500_BANK_USB = 11, + AB5500_BANK_IT = 12, + AB5500_BANK_VIBRA = 13, + AB5500_BANK_AUDIO_HEADSETUSB = 14, + AB5500_NUM_BANKS = 15, +}; + +enum ab5500_banks_addr { + AB5500_ADDR_VIT_IO_I2C_CLK_TST_OTP = 0x4A, + AB5500_ADDR_VDDDIG_IO_I2C_CLK_TST = 0x4B, + AB5500_ADDR_VDENC = 0x06, + AB5500_ADDR_SIM_USBSIM = 0x04, + AB5500_ADDR_LED = 0x10, + AB5500_ADDR_ADC = 0x0A, + AB5500_ADDR_RTC = 0x0F, + AB5500_ADDR_STARTUP = 0x03, + AB5500_ADDR_DBI_ECI = 0x07, + AB5500_ADDR_CHG = 0x0B, + AB5500_ADDR_FG_BATTCOM_ACC = 0x0C, + AB5500_ADDR_USB = 0x05, + AB5500_ADDR_IT = 0x0E, + AB5500_ADDR_VIBRA = 0x02, + AB5500_ADDR_AUDIO_HEADSETUSB = 0x0D, +}; + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB5500_IT_SOURCE0_REG 0x20 +#define AB5500_IT_SOURCE1_REG 0x21 +#define AB5500_IT_SOURCE2_REG 0x22 +#define AB5500_IT_SOURCE3_REG 0x23 +#define AB5500_IT_SOURCE4_REG 0x24 +#define AB5500_IT_SOURCE5_REG 0x25 +#define AB5500_IT_SOURCE6_REG 0x26 +#define AB5500_IT_SOURCE7_REG 0x27 +#define AB5500_IT_SOURCE8_REG 0x28 +#define AB5500_IT_SOURCE9_REG 0x29 +#define AB5500_IT_SOURCE10_REG 0x2A +#define AB5500_IT_SOURCE11_REG 0x2B +#define AB5500_IT_SOURCE12_REG 0x2C +#define AB5500_IT_SOURCE13_REG 0x2D +#define AB5500_IT_SOURCE14_REG 0x2E +#define AB5500_IT_SOURCE15_REG 0x2F +#define AB5500_IT_SOURCE16_REG 0x30 +#define AB5500_IT_SOURCE17_REG 0x31 +#define AB5500_IT_SOURCE18_REG 0x32 +#define AB5500_IT_SOURCE19_REG 0x33 +#define AB5500_IT_SOURCE20_REG 0x34 +#define AB5500_IT_SOURCE21_REG 0x35 +#define AB5500_IT_SOURCE22_REG 0x36 +#define AB5500_IT_SOURCE23_REG 0x37 +#define AB5500_IT_SOURCE24_REG 0x38 + +#define AB5500_NUM_IRQ_REGS 25 + +/** + * struct ab5500 + * @access_mutex: lock out concurrent accesses to the AB registers + * @dev: a pointer to the device struct for this chip driver + * @ab5500_irq: the analog baseband irq + * @irq_base: the platform configuration irq base for subdevices + * @chip_name: name of this chip variant + * @chip_id: 8 bit chip ID for this chip variant + * @irq_lock: a lock to protect the mask + * @abb_events: a local bit mask of the prcmu wakeup events + * @event_mask: a local copy of the mask event registers + * @last_event_mask: a copy of the last event_mask written to hardware + * @startup_events: a copy of the first reading of the event registers + * @startup_events_read: whether the first events have been read + */ +struct ab5500 { + struct mutex access_mutex; + struct device *dev; + unsigned int ab5500_irq; + unsigned int irq_base; + char chip_name[32]; + u8 chip_id; + struct mutex irq_lock; + u32 num_event_reg; + u32 abb_events; + u8 mask[AB5500_NUM_IRQ_REGS]; + u8 oldmask[AB5500_NUM_IRQ_REGS]; + u8 startup_events[AB5500_NUM_IRQ_REGS]; + bool startup_events_read; +#ifdef CONFIG_DEBUG_FS + unsigned int debug_bank; + unsigned int debug_address; +#endif +}; + +#ifndef CONFIG_AB5500_CORE +static inline int ab5500_clock_rtc_enable(int num, bool enable) +{ + return -ENOSYS; +} +#else +extern int ab5500_clock_rtc_enable(int num, bool enable); +#endif + +/* Forward Declaration */ +struct ab5500_regulator_platform_data; + +struct ab5500_platform_data { + struct {unsigned int base; unsigned int count; } irq; + void *dev_data[AB5500_NUM_DEVICES]; + size_t dev_data_sz[AB5500_NUM_DEVICES]; + struct abx500_init_settings *init_settings; + unsigned int init_settings_sz; + bool pm_power_off; + struct ab5500_regulator_platform_data *regulator; + struct ab5500_usbgpio_platform_data *usb; + struct abx500_accdet_platform_data *accdet; +}; + +struct ab5500_ponkey_platform_data { + u8 shutdown_secs; +}; +#endif /* MFD_AB5500_H */ diff --git a/include/linux/mfd/abx500/ux500_chargalg.h b/include/linux/mfd/abx500/ux500_chargalg.h new file mode 100644 index 00000000000..f04e47ff56a --- /dev/null +++ b/include/linux/mfd/abx500/ux500_chargalg.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Johan Gardsmark <johan.gardsmark@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _UX500_CHARGALG_H +#define _UX500_CHARGALG_H + +#include <linux/power_supply.h> + +#define psy_to_ux500_charger(x) container_of((x), \ + struct ux500_charger, psy) + +/* Forward declaration */ +struct ux500_charger; + +struct ux500_charger_ops { + int (*enable) (struct ux500_charger *, int, int, int); + int (*kick_wd) (struct ux500_charger *); + int (*update_curr) (struct ux500_charger *, int); +}; + +/** + * struct ux500_charger - power supply ux500 charger sub class + * @psy power supply base class + * @ops ux500 charger operations + * @max_out_volt maximum output charger voltage in mV + * @max_out_curr maximum output charger current in mA + */ +struct ux500_charger { + struct power_supply psy; + struct ux500_charger_ops ops; + int max_out_volt; + int max_out_curr; +}; + +#endif diff --git a/include/linux/mfd/db5500-prcmu.h b/include/linux/mfd/db5500-prcmu.h index f0977986402..ffbd415e6c7 100644 --- a/include/linux/mfd/db5500-prcmu.h +++ b/include/linux/mfd/db5500-prcmu.h @@ -5,21 +5,58 @@ * * U5500 PRCMU API. */ -#ifndef __MACH_PRCMU_U5500_H -#define __MACH_PRCMU_U5500_H +#ifndef __MFD_DB5500_PRCMU_H +#define __MFD_DB5500_PRCMU_H -#ifdef CONFIG_UX500_SOC_DB5500 +#ifdef CONFIG_MFD_DB5500_PRCMU void db5500_prcmu_early_init(void); - +int db5500_prcmu_set_epod(u16 epod_id, u8 epod_state); +int db5500_prcmu_set_display_clocks(void); +int db5500_prcmu_disable_dsipll(void); +int db5500_prcmu_enable_dsipll(void); int db5500_prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size); int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size); +void db5500_prcmu_enable_wakeups(u32 wakeups); +int db5500_prcmu_request_clock(u8 clock, bool enable); +void db5500_prcmu_config_abb_event_readout(u32 abb_events); +void db5500_prcmu_get_abb_event_buffer(void __iomem **buf); +int prcmu_resetout(u8 resoutn, u8 state); +int db5500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, + bool keep_ap_pll); +int db5500_prcmu_config_esram0_deep_sleep(u8 state); +void db5500_prcmu_system_reset(u16 reset_code); +u16 db5500_prcmu_get_reset_code(void); +bool db5500_prcmu_is_ac_wake_requested(void); +int db5500_prcmu_set_arm_opp(u8 opp); +int db5500_prcmu_get_arm_opp(void); +int db5500_prcmu_set_ape_opp(u8 opp); +int db5500_prcmu_get_ape_opp(void); +int db5500_prcmu_set_ddr_opp(u8 opp); +int db5500_prcmu_get_ddr_opp(void); -#else /* !CONFIG_UX500_SOC_DB5500 */ +static inline unsigned long prcmu_clock_rate(u8 clock) +{ + return 0; +} + +static inline long prcmu_round_clock_rate(u8 clock, unsigned long rate) +{ + return 0; +} -static inline void db5500_prcmu_early_init(void) +static inline int prcmu_set_clock_rate(u8 clock, unsigned long rate) { + return 0; } +int db5500_prcmu_get_hotdog(void); +int db5500_prcmu_config_hotdog(u8 threshold); +int db5500_prcmu_config_hotmon(u8 low, u8 high); +int db5500_prcmu_start_temp_sense(u16 cycles32k); +int db5500_prcmu_stop_temp_sense(void); +#else /* !CONFIG_UX500_SOC_DB5500 */ + +static inline void db5500_prcmu_early_init(void) {} static inline int db5500_prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) { @@ -31,15 +68,132 @@ static inline int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) return -ENOSYS; } -#endif /* CONFIG_UX500_SOC_DB5500 */ +static inline int db5500_prcmu_request_clock(u8 clock, bool enable) +{ + return 0; +} + +static inline unsigned long db5500_prcmu_clock_rate(u8 clock) +{ + return 0; +} + +static inline int db5500_prcmu_set_display_clocks(void) +{ + return 0; +} + +static inline int db5500_prcmu_disable_dsipll(void) +{ + return 0; +} + +static inline int db5500_prcmu_enable_dsipll(void) +{ + return 0; +} + +static inline int db5500_prcmu_config_esram0_deep_sleep(u8 state) +{ + return 0; +} + +static inline void db5500_prcmu_enable_wakeups(u32 wakeups) {} + +static inline long db5500_prcmu_round_clock_rate(u8 clock, unsigned long rate) +{ + return 0; +} + +static inline int db5500_prcmu_set_clock_rate(u8 clock, unsigned long rate) +{ + return 0; +} + +static inline int prcmu_resetout(u8 resoutn, u8 state) +{ + return 0; +} + +static inline int db5500_prcmu_set_epod(u16 epod_id, u8 epod_state) +{ + return 0; +} + +static inline void db5500_prcmu_get_abb_event_buffer(void __iomem **buf) {} +static inline void db5500_prcmu_config_abb_event_readout(u32 abb_events) {} -static inline int db5500_prcmu_config_abb_event_readout(u32 abb_events) +static inline int db5500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, + bool keep_ap_pll) { -#ifdef CONFIG_MACH_U5500_SIMULATOR return 0; -#else - return -1; -#endif } -#endif /* __MACH_PRCMU_U5500_H */ +static inline void db5500_prcmu_system_reset(u16 reset_code) {} + +static inline u16 db5500_prcmu_get_reset_code(void) +{ + return 0; +} + +static inline bool db5500_prcmu_is_ac_wake_requested(void) +{ + return 0; +} + +static inline int db5500_prcmu_set_arm_opp(u8 opp) +{ + return 0; +} + +static inline int db5500_prcmu_get_arm_opp(void) +{ + return 0; +} + +static inline int db5500_prcmu_set_ape_opp(u8 opp) +{ + return 0; +} + +static inline int db5500_prcmu_get_ape_opp(void) +{ + return 0; +} + +static inline int db5500_prcmu_set_ddr_opp(u8 opp) +{ + return 0; +} + +static inline int db5500_prcmu_get_ddr_opp(void) +{ + return 0; +} + +static inline int db5500_prcmu_get_hotdog(void) +{ + return -ENOSYS; +} +static inline int db5500_prcmu_config_hotdog(u8 threshold) +{ + return 0; +} + +static inline int db5500_prcmu_config_hotmon(u8 low, u8 high) +{ + return 0; +} + +static inline int db5500_prcmu_start_temp_sense(u16 cycles32k) +{ + return 0; +} +static inline int db5500_prcmu_stop_temp_sense(void) +{ + return 0; +} + +#endif /* CONFIG_MFD_DB5500_PRCMU */ + +#endif /* __MFD_DB5500_PRCMU_H */ diff --git a/include/linux/mfd/db8500-prcmu.h b/include/linux/mfd/db8500-prcmu.h index 917dbcab701..06623d44948 100644 --- a/include/linux/mfd/db8500-prcmu.h +++ b/include/linux/mfd/db8500-prcmu.h @@ -11,7 +11,6 @@ #define __MFD_DB8500_PRCMU_H #include <linux/interrupt.h> -#include <linux/notifier.h> /* This portion previously known as <mach/prcmu-fw-defs_v1.h> */ @@ -133,7 +132,7 @@ enum ap_pwrst { * @APEXECUTE_TO_APIDLE: Power state transition from ApExecute to ApIdle */ enum ap_pwrst_trans { - NO_TRANSITION = 0x00, + PRCMU_AP_NO_CHANGE = 0x00, APEXECUTE_TO_APSLEEP = 0x01, APIDLE_TO_APSLEEP = 0x02, /* To be removed */ PRCMU_AP_SLEEP = 0x01, @@ -146,54 +145,6 @@ enum ap_pwrst_trans { }; /** - * enum ddr_pwrst - DDR power states definition - * @DDR_PWR_STATE_UNCHANGED: SDRAM and DDR controller state is unchanged - * @DDR_PWR_STATE_ON: - * @DDR_PWR_STATE_OFFLOWLAT: - * @DDR_PWR_STATE_OFFHIGHLAT: - */ -enum ddr_pwrst { - DDR_PWR_STATE_UNCHANGED = 0x00, - DDR_PWR_STATE_ON = 0x01, - DDR_PWR_STATE_OFFLOWLAT = 0x02, - DDR_PWR_STATE_OFFHIGHLAT = 0x03 -}; - -/** - * enum arm_opp - ARM OPP states definition - * @ARM_OPP_INIT: - * @ARM_NO_CHANGE: The ARM operating point is unchanged - * @ARM_100_OPP: The new ARM operating point is arm100opp - * @ARM_50_OPP: The new ARM operating point is arm50opp - * @ARM_MAX_OPP: Operating point is "max" (more than 100) - * @ARM_MAX_FREQ100OPP: Set max opp if available, else 100 - * @ARM_EXTCLK: The new ARM operating point is armExtClk - */ -enum arm_opp { - ARM_OPP_INIT = 0x00, - ARM_NO_CHANGE = 0x01, - ARM_100_OPP = 0x02, - ARM_50_OPP = 0x03, - ARM_MAX_OPP = 0x04, - ARM_MAX_FREQ100OPP = 0x05, - ARM_EXTCLK = 0x07 -}; - -/** - * enum ape_opp - APE OPP states definition - * @APE_OPP_INIT: - * @APE_NO_CHANGE: The APE operating point is unchanged - * @APE_100_OPP: The new APE operating point is ape100opp - * @APE_50_OPP: 50% - */ -enum ape_opp { - APE_OPP_INIT = 0x00, - APE_NO_CHANGE = 0x01, - APE_100_OPP = 0x02, - APE_50_OPP = 0x03 -}; - -/** * enum hw_acc_state - State definition for hardware accelerator * @HW_NO_CHANGE: The hardware accelerator state must remain unchanged * @HW_OFF: The hardware accelerator must be switched off @@ -469,26 +420,6 @@ enum auto_enable { /* End of file previously known as prcmu-fw-defs_v1.h */ -/* PRCMU Wakeup defines */ -enum prcmu_wakeup_index { - PRCMU_WAKEUP_INDEX_RTC, - PRCMU_WAKEUP_INDEX_RTT0, - PRCMU_WAKEUP_INDEX_RTT1, - PRCMU_WAKEUP_INDEX_HSI0, - PRCMU_WAKEUP_INDEX_HSI1, - PRCMU_WAKEUP_INDEX_USB, - PRCMU_WAKEUP_INDEX_ABB, - PRCMU_WAKEUP_INDEX_ABB_FIFO, - PRCMU_WAKEUP_INDEX_ARM, - NUM_PRCMU_WAKEUP_INDICES -}; -#define PRCMU_WAKEUP(_name) (BIT(PRCMU_WAKEUP_INDEX_##_name)) - -/* PRCMU QoS APE OPP class */ -#define PRCMU_QOS_APE_OPP 1 -#define PRCMU_QOS_DDR_OPP 2 -#define PRCMU_QOS_DEFAULT_VALUE -1 - /** * enum hw_acc_dev - enum for hw accelerators * @HW_ACC_SVAMMDSP: for SVAMMDSP @@ -527,64 +458,6 @@ enum hw_acc_dev { }; /* - * Ids for all EPODs (power domains) - * - EPOD_ID_SVAMMDSP: power domain for SVA MMDSP - * - EPOD_ID_SVAPIPE: power domain for SVA pipe - * - EPOD_ID_SIAMMDSP: power domain for SIA MMDSP - * - EPOD_ID_SIAPIPE: power domain for SIA pipe - * - EPOD_ID_SGA: power domain for SGA - * - EPOD_ID_B2R2_MCDE: power domain for B2R2 and MCDE - * - EPOD_ID_ESRAM12: power domain for ESRAM 1 and 2 - * - EPOD_ID_ESRAM34: power domain for ESRAM 3 and 4 - * - NUM_EPOD_ID: number of power domains - */ -#define EPOD_ID_SVAMMDSP 0 -#define EPOD_ID_SVAPIPE 1 -#define EPOD_ID_SIAMMDSP 2 -#define EPOD_ID_SIAPIPE 3 -#define EPOD_ID_SGA 4 -#define EPOD_ID_B2R2_MCDE 5 -#define EPOD_ID_ESRAM12 6 -#define EPOD_ID_ESRAM34 7 -#define NUM_EPOD_ID 8 - -/* - * state definition for EPOD (power domain) - * - EPOD_STATE_NO_CHANGE: The EPOD should remain unchanged - * - EPOD_STATE_OFF: The EPOD is switched off - * - EPOD_STATE_RAMRET: The EPOD is switched off with its internal RAM in - * retention - * - EPOD_STATE_ON_CLK_OFF: The EPOD is switched on, clock is still off - * - EPOD_STATE_ON: Same as above, but with clock enabled - */ -#define EPOD_STATE_NO_CHANGE 0x00 -#define EPOD_STATE_OFF 0x01 -#define EPOD_STATE_RAMRET 0x02 -#define EPOD_STATE_ON_CLK_OFF 0x03 -#define EPOD_STATE_ON 0x04 - -/* - * CLKOUT sources - */ -#define PRCMU_CLKSRC_CLK38M 0x00 -#define PRCMU_CLKSRC_ACLK 0x01 -#define PRCMU_CLKSRC_SYSCLK 0x02 -#define PRCMU_CLKSRC_LCDCLK 0x03 -#define PRCMU_CLKSRC_SDMMCCLK 0x04 -#define PRCMU_CLKSRC_TVCLK 0x05 -#define PRCMU_CLKSRC_TIMCLK 0x06 -#define PRCMU_CLKSRC_CLK009 0x07 -/* These are only valid for CLKOUT1: */ -#define PRCMU_CLKSRC_SIAMMDSPCLK 0x40 -#define PRCMU_CLKSRC_I2CCLK 0x41 -#define PRCMU_CLKSRC_MSP02CLK 0x42 -#define PRCMU_CLKSRC_ARMPLL_OBSCLK 0x43 -#define PRCMU_CLKSRC_HSIRXCLK 0x44 -#define PRCMU_CLKSRC_HSITXCLK 0x45 -#define PRCMU_CLKSRC_ARMCLKFIX 0x46 -#define PRCMU_CLKSRC_HDMICLK 0x47 - -/* * Definitions for autonomous power management configuration. */ @@ -620,126 +493,66 @@ struct prcmu_auto_pm_config { u8 sva_policy; }; -/** - * enum ddr_opp - DDR OPP states definition - * @DDR_100_OPP: The new DDR operating point is ddr100opp - * @DDR_50_OPP: The new DDR operating point is ddr50opp - * @DDR_25_OPP: The new DDR operating point is ddr25opp - */ -enum ddr_opp { - DDR_100_OPP = 0x00, - DDR_50_OPP = 0x01, - DDR_25_OPP = 0x02, -}; - -/* - * Clock identifiers. - */ -enum prcmu_clock { - PRCMU_SGACLK, - PRCMU_UARTCLK, - PRCMU_MSP02CLK, - PRCMU_MSP1CLK, - PRCMU_I2CCLK, - PRCMU_SDMMCCLK, - PRCMU_SLIMCLK, - PRCMU_PER1CLK, - PRCMU_PER2CLK, - PRCMU_PER3CLK, - PRCMU_PER5CLK, - PRCMU_PER6CLK, - PRCMU_PER7CLK, - PRCMU_LCDCLK, - PRCMU_BMLCLK, - PRCMU_HSITXCLK, - PRCMU_HSIRXCLK, - PRCMU_HDMICLK, - PRCMU_APEATCLK, - PRCMU_APETRACECLK, - PRCMU_MCDECLK, - PRCMU_IPI2CCLK, - PRCMU_DSIALTCLK, - PRCMU_DMACLK, - PRCMU_B2R2CLK, - PRCMU_TVCLK, - PRCMU_SSPCLK, - PRCMU_RNGCLK, - PRCMU_UICCCLK, - PRCMU_NUM_REG_CLOCKS, - PRCMU_SYSCLK = PRCMU_NUM_REG_CLOCKS, - PRCMU_TIMCLK, -}; - -/* - * Definitions for controlling ESRAM0 in deep sleep. - */ -#define ESRAM0_DEEP_SLEEP_STATE_OFF 1 -#define ESRAM0_DEEP_SLEEP_STATE_RET 2 - -#ifdef CONFIG_MFD_DB8500_PRCMU -void __init prcmu_early_init(void); -int prcmu_set_display_clocks(void); -int prcmu_disable_dsipll(void); -int prcmu_enable_dsipll(void); -#else -static inline void __init prcmu_early_init(void) {} -#endif - #ifdef CONFIG_MFD_DB8500_PRCMU +void db8500_prcmu_early_init(void); int prcmu_set_rc_a2p(enum romcode_write); enum romcode_read prcmu_get_rc_p2a(void); enum ap_pwrst prcmu_get_xp70_current_state(void); -int prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll); - -void prcmu_enable_wakeups(u32 wakeups); -static inline void prcmu_disable_wakeups(void) -{ - prcmu_enable_wakeups(0); -} - -void prcmu_config_abb_event_readout(u32 abb_events); -void prcmu_get_abb_event_buffer(void __iomem **buf); -int prcmu_set_arm_opp(u8 opp); -int prcmu_get_arm_opp(void); bool prcmu_has_arm_maxopp(void); bool prcmu_is_u8400(void); -int prcmu_set_ape_opp(u8 opp); -int prcmu_get_ape_opp(void); int prcmu_request_ape_opp_100_voltage(bool enable); int prcmu_release_usb_wakeup_state(void); -int prcmu_set_ddr_opp(u8 opp); -int prcmu_get_ddr_opp(void); -unsigned long prcmu_qos_get_cpufreq_opp_delay(void); -void prcmu_qos_set_cpufreq_opp_delay(unsigned long); /* NOTE! Use regulator framework instead */ int prcmu_set_hwacc(u16 hw_acc_dev, u8 state); -int prcmu_set_epod(u16 epod_id, u8 epod_state); void prcmu_configure_auto_pm(struct prcmu_auto_pm_config *sleep, struct prcmu_auto_pm_config *idle); bool prcmu_is_auto_pm_enabled(void); -int prcmu_config_clkout(u8 clkout, u8 source, u8 div); -int prcmu_request_clock(u8 clock, bool enable); -int prcmu_set_clock_divider(u8 clock, u8 divider); -int prcmu_config_esram0_deep_sleep(u8 state); -int prcmu_config_hotdog(u8 threshold); -int prcmu_config_hotmon(u8 low, u8 high); -int prcmu_start_temp_sense(u16 cycles32k); -int prcmu_stop_temp_sense(void); + +int db8500_prcmu_config_hotdog(u8 threshold); +int db8500_prcmu_config_hotmon(u8 low, u8 high); +int db8500_prcmu_start_temp_sense(u16 cycles32k); +int db8500_prcmu_stop_temp_sense(void); int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size); int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size); void prcmu_ac_wake_req(void); void prcmu_ac_sleep_req(void); -void prcmu_system_reset(u16 reset_code); void prcmu_modem_reset(void); -bool prcmu_is_ac_wake_requested(void); void prcmu_enable_spi2(void); void prcmu_disable_spi2(void); +int prcmu_config_a9wdog(u8 num, bool sleep_auto_off); +int prcmu_enable_a9wdog(u8 id); +int prcmu_disable_a9wdog(u8 id); +int prcmu_kick_a9wdog(u8 id); +int prcmu_load_a9wdog(u8 id, u32 val); + +void db8500_prcmu_system_reset(u16 reset_code); +int db8500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll); +void db8500_prcmu_enable_wakeups(u32 wakeups); +int db8500_prcmu_set_epod(u16 epod_id, u8 epod_state); +int db8500_prcmu_request_clock(u8 clock, bool enable); +int db8500_prcmu_set_display_clocks(void); +int db8500_prcmu_disable_dsipll(void); +int db8500_prcmu_enable_dsipll(void); +void db8500_prcmu_config_abb_event_readout(u32 abb_events); +void db8500_prcmu_get_abb_event_buffer(void __iomem **buf); +int db8500_prcmu_config_esram0_deep_sleep(u8 state); +u16 db8500_prcmu_get_reset_code(void); +bool db8500_prcmu_is_ac_wake_requested(void); +int db8500_prcmu_set_arm_opp(u8 opp); +int db8500_prcmu_get_arm_opp(void); +int db8500_prcmu_set_ape_opp(u8 opp); +int db8500_prcmu_get_ape_opp(void); +int db8500_prcmu_set_ddr_opp(u8 opp); +int db8500_prcmu_get_ddr_opp(void); + #else /* !CONFIG_MFD_DB8500_PRCMU */ +static inline void db8500_prcmu_early_init(void) {} + static inline int prcmu_set_rc_a2p(enum romcode_write code) { return 0; @@ -755,44 +568,22 @@ static inline enum ap_pwrst prcmu_get_xp70_current_state(void) return AP_EXECUTE; } -static inline int prcmu_set_power_state(u8 state, bool keep_ulp_clk, - bool keep_ap_pll) -{ - return 0; -} - -static inline void prcmu_enable_wakeups(u32 wakeups) {} - -static inline void prcmu_disable_wakeups(void) {} - -static inline void prcmu_config_abb_event_readout(u32 abb_events) {} - -static inline int prcmu_set_arm_opp(u8 opp) -{ - return 0; -} - -static inline int prcmu_get_arm_opp(void) -{ - return ARM_100_OPP; -} - -static bool prcmu_has_arm_maxopp(void) +static inline bool prcmu_has_arm_maxopp(void) { return false; } -static bool prcmu_is_u8400(void) +static inline bool prcmu_is_u8400(void) { return false; } -static inline int prcmu_set_ape_opp(u8 opp) +static inline int db8500_prcmu_set_ape_opp(u8 opp) { return 0; } -static inline int prcmu_get_ape_opp(void) +static inline int db8500_prcmu_get_ape_opp(void) { return APE_100_OPP; } @@ -807,28 +598,20 @@ static inline int prcmu_release_usb_wakeup_state(void) return 0; } -static inline int prcmu_set_ddr_opp(u8 opp) +static inline int db8500_prcmu_set_ddr_opp(u8 opp) { return 0; } -static inline int prcmu_get_ddr_opp(void) +static inline int db8500_prcmu_get_ddr_opp(void) { return DDR_100_OPP; } -static inline unsigned long prcmu_qos_get_cpufreq_opp_delay(void) -{ - return 0; -} - -static inline void prcmu_qos_set_cpufreq_opp_delay(unsigned long n) {} - static inline int prcmu_set_hwacc(u16 hw_acc_dev, u8 state) { return 0; } - static inline void prcmu_configure_auto_pm(struct prcmu_auto_pm_config *sleep, struct prcmu_auto_pm_config *idle) { @@ -839,140 +622,141 @@ static inline bool prcmu_is_auto_pm_enabled(void) return false; } -static inline int prcmu_config_clkout(u8 clkout, u8 source, u8 div) +static inline int db8500_prcmu_config_hotdog(u8 threshold) { return 0; } -static inline int prcmu_request_clock(u8 clock, bool enable) +static inline int db8500_prcmu_config_hotmon(u8 low, u8 high) { return 0; } -static inline int prcmu_set_clock_divider(u8 clock, u8 divider) +static inline int db8500_prcmu_start_temp_sense(u16 cycles32k) { return 0; } -int prcmu_config_esram0_deep_sleep(u8 state) +static inline int db8500_prcmu_stop_temp_sense(void) { return 0; } -static inline int prcmu_config_hotdog(u8 threshold) +static inline int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) { - return 0; + return -ENOSYS; } -static inline int prcmu_config_hotmon(u8 low, u8 high) +static inline int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) { - return 0; + return -ENOSYS; } -static inline int prcmu_start_temp_sense(u16 cycles32k) +static inline void prcmu_ac_wake_req(void) {} + +static inline void prcmu_ac_sleep_req(void) {} + +static inline void prcmu_modem_reset(void) {} + +static inline int prcmu_enable_spi2(void) { return 0; } -static inline int prcmu_stop_temp_sense(void) +static inline int prcmu_disable_spi2(void) { return 0; } -static inline int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) -{ - return -ENOSYS; -} +static inline void db8500_prcmu_system_reset(u16 reset_code) {} -static inline int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) +static inline int db8500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, + bool keep_ap_pll) { - return -ENOSYS; + return 0; } -static inline void prcmu_ac_wake_req(void) {} - -static inline void prcmu_ac_sleep_req(void) {} +static inline void db8500_prcmu_enable_wakeups(u32 wakeups) {} -static inline void prcmu_system_reset(u16 reset_code) {} +static inline int db8500_prcmu_set_epod(u16 epod_id, u8 epod_state) +{ + return 0; +} -static inline void prcmu_modem_reset(void) {} +static inline int db8500_prcmu_request_clock(u8 clock, bool enable) +{ + return 0; +} -static inline bool prcmu_is_ac_wake_requested(void) +static inline int db8500_prcmu_set_display_clocks(void) { - return false; + return 0; } -#ifndef CONFIG_UX500_SOC_DB5500 -static inline int prcmu_set_display_clocks(void) +static inline int db8500_prcmu_disable_dsipll(void) { return 0; } -static inline int prcmu_disable_dsipll(void) +static inline int db8500_prcmu_enable_dsipll(void) { return 0; } -static inline int prcmu_enable_dsipll(void) +static inline int db8500_prcmu_config_esram0_deep_sleep(u8 state) { return 0; } -#endif -static inline int prcmu_enable_spi2(void) +static inline void db8500_prcmu_config_abb_event_readout(u32 abb_events) {} + +static inline void db8500_prcmu_get_abb_event_buffer(void __iomem **buf) {} + +static inline u16 db8500_prcmu_get_reset_code(void) { return 0; } -static inline int prcmu_disable_spi2(void) +static inline int prcmu_config_a9wdog(u8 num, bool sleep_auto_off) { return 0; } -#endif /* !CONFIG_MFD_DB8500_PRCMU */ +static inline int prcmu_enable_a9wdog(u8 id) +{ + return 0; +} -#ifdef CONFIG_UX500_PRCMU_QOS_POWER -int prcmu_qos_requirement(int pm_qos_class); -int prcmu_qos_add_requirement(int pm_qos_class, char *name, s32 value); -int prcmu_qos_update_requirement(int pm_qos_class, char *name, s32 new_value); -void prcmu_qos_remove_requirement(int pm_qos_class, char *name); -int prcmu_qos_add_notifier(int prcmu_qos_class, - struct notifier_block *notifier); -int prcmu_qos_remove_notifier(int prcmu_qos_class, - struct notifier_block *notifier); -#else -static inline int prcmu_qos_requirement(int prcmu_qos_class) +static inline int prcmu_disable_a9wdog(u8 id) { return 0; } -static inline int prcmu_qos_add_requirement(int prcmu_qos_class, - char *name, s32 value) +static inline int prcmu_kick_a9wdog(u8 id) { return 0; } -static inline int prcmu_qos_update_requirement(int prcmu_qos_class, - char *name, s32 new_value) +static inline int prcmu_load_a9wdog(u8 id, u32 val) { return 0; } -static inline void prcmu_qos_remove_requirement(int prcmu_qos_class, char *name) +static inline bool db8500_prcmu_is_ac_wake_requested(void) { + return 0; } -static inline int prcmu_qos_add_notifier(int prcmu_qos_class, - struct notifier_block *notifier) +static inline int db8500_prcmu_set_arm_opp(u8 opp) { return 0; } -static inline int prcmu_qos_remove_notifier(int prcmu_qos_class, - struct notifier_block *notifier) + +static inline int db8500_prcmu_get_arm_opp(void) { return 0; } -#endif +#endif /* !CONFIG_MFD_DB8500_PRCMU */ #endif /* __MFD_DB8500_PRCMU_H */ diff --git a/include/linux/mfd/dbx500-prcmu.h b/include/linux/mfd/dbx500-prcmu.h new file mode 100644 index 00000000000..4cbd6a8aeed --- /dev/null +++ b/include/linux/mfd/dbx500-prcmu.h @@ -0,0 +1,678 @@ +/* + * Copyright (C) ST Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * + * STE Ux500 PRCMU API + */ +#ifndef __MACH_PRCMU_H +#define __MACH_PRCMU_H + +#include <linux/interrupt.h> +#include <linux/notifier.h> +#include <linux/err.h> + +/* PRCMU Wakeup defines */ +enum prcmu_wakeup_index { + PRCMU_WAKEUP_INDEX_RTC, + PRCMU_WAKEUP_INDEX_RTT0, + PRCMU_WAKEUP_INDEX_RTT1, + PRCMU_WAKEUP_INDEX_HSI0, + PRCMU_WAKEUP_INDEX_HSI1, + PRCMU_WAKEUP_INDEX_USB, + PRCMU_WAKEUP_INDEX_ABB, + PRCMU_WAKEUP_INDEX_ABB_FIFO, + PRCMU_WAKEUP_INDEX_ARM, + PRCMU_WAKEUP_INDEX_CD_IRQ, + NUM_PRCMU_WAKEUP_INDICES +}; +#define PRCMU_WAKEUP(_name) (BIT(PRCMU_WAKEUP_INDEX_##_name)) + +/* EPOD (power domain) IDs */ + +/* + * DB8500 EPODs + * - EPOD_ID_SVAMMDSP: power domain for SVA MMDSP + * - EPOD_ID_SVAPIPE: power domain for SVA pipe + * - EPOD_ID_SIAMMDSP: power domain for SIA MMDSP + * - EPOD_ID_SIAPIPE: power domain for SIA pipe + * - EPOD_ID_SGA: power domain for SGA + * - EPOD_ID_B2R2_MCDE: power domain for B2R2 and MCDE + * - EPOD_ID_ESRAM12: power domain for ESRAM 1 and 2 + * - EPOD_ID_ESRAM34: power domain for ESRAM 3 and 4 + * - NUM_EPOD_ID: number of power domains + * + * TODO: These should be prefixed. + */ +#define EPOD_ID_SVAMMDSP 0 +#define EPOD_ID_SVAPIPE 1 +#define EPOD_ID_SIAMMDSP 2 +#define EPOD_ID_SIAPIPE 3 +#define EPOD_ID_SGA 4 +#define EPOD_ID_B2R2_MCDE 5 +#define EPOD_ID_ESRAM12 6 +#define EPOD_ID_ESRAM34 7 +#define NUM_EPOD_ID 8 + +/* + * DB5500 EPODs + */ +#define DB5500_EPOD_ID_BASE 0x0100 +#define DB5500_EPOD_ID_SGA (DB5500_EPOD_ID_BASE + 0) +#define DB5500_EPOD_ID_HVA (DB5500_EPOD_ID_BASE + 1) +#define DB5500_EPOD_ID_SIA (DB5500_EPOD_ID_BASE + 2) +#define DB5500_EPOD_ID_DISP (DB5500_EPOD_ID_BASE + 3) +#define DB5500_EPOD_ID_ESRAM12 (DB5500_EPOD_ID_BASE + 6) +#define DB5500_NUM_EPOD_ID 7 + +/* + * state definition for EPOD (power domain) + * - EPOD_STATE_NO_CHANGE: The EPOD should remain unchanged + * - EPOD_STATE_OFF: The EPOD is switched off + * - EPOD_STATE_RAMRET: The EPOD is switched off with its internal RAM in + * retention + * - EPOD_STATE_ON_CLK_OFF: The EPOD is switched on, clock is still off + * - EPOD_STATE_ON: Same as above, but with clock enabled + */ +#define EPOD_STATE_NO_CHANGE 0x00 +#define EPOD_STATE_OFF 0x01 +#define EPOD_STATE_RAMRET 0x02 +#define EPOD_STATE_ON_CLK_OFF 0x03 +#define EPOD_STATE_ON 0x04 + +/* DB5500 CLKOUT IDs */ +enum { + DB5500_CLKOUT0 = 0, + DB5500_CLKOUT1, +}; + +/* DB5500 CLKOUTx sources */ +enum { + DB5500_CLKOUT_REF_CLK_SEL0, + DB5500_CLKOUT_RTC_CLK0_SEL0, + DB5500_CLKOUT_ULP_CLK_SEL0, + DB5500_CLKOUT_STATIC0, + DB5500_CLKOUT_REFCLK, + DB5500_CLKOUT_ULPCLK, + DB5500_CLKOUT_ARMCLK, + DB5500_CLKOUT_SYSACC0CLK, + DB5500_CLKOUT_SOC0PLLCLK, + DB5500_CLKOUT_SOC1PLLCLK, + DB5500_CLKOUT_DDRPLLCLK, + DB5500_CLKOUT_TVCLK, + DB5500_CLKOUT_IRDACLK, +}; + +/* + * CLKOUT sources + */ +#define PRCMU_CLKSRC_CLK38M 0x00 +#define PRCMU_CLKSRC_ACLK 0x01 +#define PRCMU_CLKSRC_SYSCLK 0x02 +#define PRCMU_CLKSRC_LCDCLK 0x03 +#define PRCMU_CLKSRC_SDMMCCLK 0x04 +#define PRCMU_CLKSRC_TVCLK 0x05 +#define PRCMU_CLKSRC_TIMCLK 0x06 +#define PRCMU_CLKSRC_CLK009 0x07 +/* These are only valid for CLKOUT1: */ +#define PRCMU_CLKSRC_SIAMMDSPCLK 0x40 +#define PRCMU_CLKSRC_I2CCLK 0x41 +#define PRCMU_CLKSRC_MSP02CLK 0x42 +#define PRCMU_CLKSRC_ARMPLL_OBSCLK 0x43 +#define PRCMU_CLKSRC_HSIRXCLK 0x44 +#define PRCMU_CLKSRC_HSITXCLK 0x45 +#define PRCMU_CLKSRC_ARMCLKFIX 0x46 +#define PRCMU_CLKSRC_HDMICLK 0x47 + +/* + * Clock identifiers. + */ +enum prcmu_clock { + PRCMU_SGACLK, + PRCMU_UARTCLK, + PRCMU_MSP02CLK, + PRCMU_MSP1CLK, + PRCMU_I2CCLK, + PRCMU_SDMMCCLK, + PRCMU_SPARE1CLK, + PRCMU_SLIMCLK, + PRCMU_PER1CLK, + PRCMU_PER2CLK, + PRCMU_PER3CLK, + PRCMU_PER5CLK, + PRCMU_PER6CLK, + PRCMU_PER7CLK, + PRCMU_LCDCLK, + PRCMU_BMLCLK, + PRCMU_HSITXCLK, + PRCMU_HSIRXCLK, + PRCMU_HDMICLK, + PRCMU_APEATCLK, + PRCMU_APETRACECLK, + PRCMU_MCDECLK, + PRCMU_IPI2CCLK, + PRCMU_DSIALTCLK, + PRCMU_DMACLK, + PRCMU_B2R2CLK, + PRCMU_TVCLK, + PRCMU_SSPCLK, + PRCMU_RNGCLK, + PRCMU_UICCCLK, + PRCMU_PWMCLK, + PRCMU_IRDACLK, + PRCMU_IRRCCLK, + PRCMU_SIACLK, + PRCMU_SVACLK, + PRCMU_NUM_REG_CLOCKS, + PRCMU_SYSCLK = PRCMU_NUM_REG_CLOCKS, + PRCMU_CDCLK, + PRCMU_TIMCLK, + PRCMU_PLLSOC0, + PRCMU_PLLSOC1, + PRCMU_PLLDDR, +}; + +/** + * enum ape_opp - APE OPP states definition + * @APE_OPP_INIT: + * @APE_NO_CHANGE: The APE operating point is unchanged + * @APE_100_OPP: The new APE operating point is ape100opp + * @APE_50_OPP: 50% + * @APE_50_PARTLY_25_OPP: 50%, except some clocks at 25%. + */ +enum ape_opp { + APE_OPP_INIT = 0x00, + APE_NO_CHANGE = 0x01, + APE_100_OPP = 0x02, + APE_50_OPP = 0x03, + APE_50_PARTLY_25_OPP = 0xFF, +}; + +/** + * enum arm_opp - ARM OPP states definition + * @ARM_OPP_INIT: + * @ARM_NO_CHANGE: The ARM operating point is unchanged + * @ARM_100_OPP: The new ARM operating point is arm100opp + * @ARM_50_OPP: The new ARM operating point is arm50opp + * @ARM_MAX_OPP: Operating point is "max" (more than 100) + * @ARM_MAX_FREQ100OPP: Set max opp if available, else 100 + * @ARM_EXTCLK: The new ARM operating point is armExtClk + */ +enum arm_opp { + ARM_OPP_INIT = 0x00, + ARM_NO_CHANGE = 0x01, + ARM_100_OPP = 0x02, + ARM_50_OPP = 0x03, + ARM_MAX_OPP = 0x04, + ARM_MAX_FREQ100OPP = 0x05, + ARM_EXTCLK = 0x07 +}; + +/** + * enum ddr_opp - DDR OPP states definition + * @DDR_100_OPP: The new DDR operating point is ddr100opp + * @DDR_50_OPP: The new DDR operating point is ddr50opp + * @DDR_25_OPP: The new DDR operating point is ddr25opp + */ +enum ddr_opp { + DDR_100_OPP = 0x00, + DDR_50_OPP = 0x01, + DDR_25_OPP = 0x02, +}; + +/* + * Definitions for controlling ESRAM0 in deep sleep. + */ +#define ESRAM0_DEEP_SLEEP_STATE_OFF 1 +#define ESRAM0_DEEP_SLEEP_STATE_RET 2 + +/** + * enum ddr_pwrst - DDR power states definition + * @DDR_PWR_STATE_UNCHANGED: SDRAM and DDR controller state is unchanged + * @DDR_PWR_STATE_ON: + * @DDR_PWR_STATE_OFFLOWLAT: + * @DDR_PWR_STATE_OFFHIGHLAT: + */ +enum ddr_pwrst { + DDR_PWR_STATE_UNCHANGED = 0x00, + DDR_PWR_STATE_ON = 0x01, + DDR_PWR_STATE_OFFLOWLAT = 0x02, + DDR_PWR_STATE_OFFHIGHLAT = 0x03 +}; + +#include <linux/mfd/db8500-prcmu.h> +#include <linux/mfd/db5500-prcmu.h> + +#if defined(CONFIG_UX500_SOC_DB8500) || defined(CONFIG_UX500_SOC_DB5500) + +#include <mach/id.h> + +static inline void __init prcmu_early_init(void) +{ + if (cpu_is_u5500()) + return db5500_prcmu_early_init(); + else + return db8500_prcmu_early_init(); +} + +static inline int prcmu_set_power_state(u8 state, bool keep_ulp_clk, + bool keep_ap_pll) +{ + if (cpu_is_u5500()) + return db5500_prcmu_set_power_state(state, keep_ulp_clk, + keep_ap_pll); + else + return db8500_prcmu_set_power_state(state, keep_ulp_clk, + keep_ap_pll); +} + +static inline int prcmu_set_epod(u16 epod_id, u8 epod_state) +{ + if (cpu_is_u5500()) + return db5500_prcmu_set_epod(epod_id, epod_state); + else + return db8500_prcmu_set_epod(epod_id, epod_state); +} + +static inline void prcmu_enable_wakeups(u32 wakeups) +{ + if (cpu_is_u5500()) + db5500_prcmu_enable_wakeups(wakeups); + else + db8500_prcmu_enable_wakeups(wakeups); +} + +static inline void prcmu_disable_wakeups(void) +{ + prcmu_enable_wakeups(0); +} + +static inline void prcmu_config_abb_event_readout(u32 abb_events) +{ + if (cpu_is_u5500()) + db5500_prcmu_config_abb_event_readout(abb_events); + else + db8500_prcmu_config_abb_event_readout(abb_events); +} + +static inline void prcmu_get_abb_event_buffer(void __iomem **buf) +{ + if (cpu_is_u5500()) + db5500_prcmu_get_abb_event_buffer(buf); + else + db8500_prcmu_get_abb_event_buffer(buf); +} + +int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size); +int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size); + +int prcmu_config_clkout(u8 clkout, u8 source, u8 div); + +static inline int prcmu_request_clock(u8 clock, bool enable) +{ + if (cpu_is_u5500()) + return db5500_prcmu_request_clock(clock, enable); + else + return db8500_prcmu_request_clock(clock, enable); +} + +unsigned long prcmu_clock_rate(u8 clock); +long prcmu_round_clock_rate(u8 clock, unsigned long rate); +int prcmu_set_clock_rate(u8 clock, unsigned long rate); + +static inline int prcmu_set_ddr_opp(u8 opp) +{ + if (cpu_is_u5500()) + return db5500_prcmu_set_ddr_opp(opp); + else + return db8500_prcmu_set_ddr_opp(opp); +} +static inline int prcmu_get_ddr_opp(void) +{ + if (cpu_is_u5500()) + return db5500_prcmu_get_ddr_opp(); + else + return db8500_prcmu_get_ddr_opp(); +} + +static inline int prcmu_set_arm_opp(u8 opp) +{ + if (cpu_is_u5500()) + return db5500_prcmu_set_arm_opp(opp); + else + return db8500_prcmu_set_arm_opp(opp); +} + +static inline int prcmu_get_arm_opp(void) +{ + if (cpu_is_u5500()) + return db5500_prcmu_get_arm_opp(); + else + return db8500_prcmu_get_arm_opp(); +} + +static inline int prcmu_set_ape_opp(u8 opp) +{ + if (cpu_is_u5500()) + return db5500_prcmu_set_ape_opp(opp); + else + return db8500_prcmu_set_ape_opp(opp); +} + +static inline int prcmu_get_ape_opp(void) +{ + if (cpu_is_u5500()) + return db5500_prcmu_get_ape_opp(); + else + return db8500_prcmu_get_ape_opp(); +} + +static inline void prcmu_system_reset(u16 reset_code) +{ + if (cpu_is_u5500()) + return db5500_prcmu_system_reset(reset_code); + else + return db8500_prcmu_system_reset(reset_code); +} + +static inline u16 prcmu_get_reset_code(void) +{ + if (cpu_is_u5500()) + return db5500_prcmu_get_reset_code(); + else + return db8500_prcmu_get_reset_code(); +} + +void prcmu_ac_wake_req(void); +void prcmu_ac_sleep_req(void); +void prcmu_modem_reset(void); +static inline bool prcmu_is_ac_wake_requested(void) +{ + if (cpu_is_u5500()) + return db5500_prcmu_is_ac_wake_requested(); + else + return db8500_prcmu_is_ac_wake_requested(); +} + +static inline int prcmu_set_display_clocks(void) +{ + if (cpu_is_u5500()) + return db5500_prcmu_set_display_clocks(); + else + return db8500_prcmu_set_display_clocks(); +} + +static inline int prcmu_disable_dsipll(void) +{ + if (cpu_is_u5500()) + return db5500_prcmu_disable_dsipll(); + else + return db8500_prcmu_disable_dsipll(); +} + +static inline int prcmu_enable_dsipll(void) +{ + if (cpu_is_u5500()) + return db5500_prcmu_enable_dsipll(); + else + return db8500_prcmu_enable_dsipll(); +} + +static inline int prcmu_config_esram0_deep_sleep(u8 state) +{ + if (cpu_is_u5500()) + return db5500_prcmu_config_esram0_deep_sleep(state); + else + return db8500_prcmu_config_esram0_deep_sleep(state); +} + +static inline int prcmu_config_hotdog(u8 threshold) +{ + if (cpu_is_u5500()) + return db5500_prcmu_config_hotdog(threshold); + else + return db8500_prcmu_config_hotdog(threshold); +} + +static inline int prcmu_config_hotmon(u8 low, u8 high) +{ + if (cpu_is_u5500()) + return db5500_prcmu_config_hotmon(low, high); + else + return db8500_prcmu_config_hotmon(low, high); +} + +static inline int prcmu_start_temp_sense(u16 cycles32k) +{ + if (cpu_is_u5500()) + return db5500_prcmu_start_temp_sense(cycles32k); + else + return db8500_prcmu_start_temp_sense(cycles32k); +} + +static inline int prcmu_stop_temp_sense(void) +{ + if (cpu_is_u5500()) + return db5500_prcmu_stop_temp_sense(); + else + return db8500_prcmu_stop_temp_sense(); +} + +#else + +static inline void __init prcmu_early_init(void) {} + +static inline int prcmu_set_power_state(u8 state, bool keep_ulp_clk, + bool keep_ap_pll) +{ + return 0; +} + +static inline int prcmu_set_epod(u16 epod_id, u8 epod_state) +{ + return 0; +} + +static inline void prcmu_enable_wakeups(u32 wakeups) {} + +static inline void prcmu_disable_wakeups(void) {} + +static inline int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) +{ + return -ENOSYS; +} + +static inline int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) +{ + return -ENOSYS; +} + +static inline int prcmu_config_clkout(u8 clkout, u8 source, u8 div) +{ + return 0; +} + +static inline int prcmu_request_clock(u8 clock, bool enable) +{ + return 0; +} + +static inline long prcmu_round_clock_rate(u8 clock, unsigned long rate) +{ + return 0; +} + +static inline int prcmu_set_clock_rate(u8 clock, unsigned long rate) +{ + return 0; +} + +static inline unsigned long prcmu_clock_rate(u8 clock) +{ + return 0; +} + +static inline int prcmu_set_ape_opp(u8 opp) +{ + return 0; +} + +static inline int prcmu_get_ape_opp(void) +{ + return APE_100_OPP; +} + +static inline int prcmu_set_arm_opp(u8 opp) +{ + return 0; +} + +static inline int prcmu_get_arm_opp(void) +{ + return ARM_100_OPP; +} + +static inline int prcmu_set_ddr_opp(u8 opp) +{ + return 0; +} + +static inline int prcmu_get_ddr_opp(void) +{ + return DDR_100_OPP; +} + +static inline void prcmu_system_reset(u16 reset_code) {} + +static inline u16 prcmu_get_reset_code(void) +{ + return 0; +} + +static inline void prcmu_ac_wake_req(void) {} + +static inline void prcmu_ac_sleep_req(void) {} + +static inline void prcmu_modem_reset(void) {} + +static inline bool prcmu_is_ac_wake_requested(void) +{ + return false; +} + +static inline int prcmu_set_display_clocks(void) +{ + return 0; +} + +static inline int prcmu_disable_dsipll(void) +{ + return 0; +} + +static inline int prcmu_enable_dsipll(void) +{ + return 0; +} + +static inline int prcmu_config_esram0_deep_sleep(u8 state) +{ + return 0; +} + +static inline void prcmu_config_abb_event_readout(u32 abb_events) {} + +static inline void prcmu_get_abb_event_buffer(void __iomem **buf) +{ + *buf = NULL; +} + +static inline int prcmu_config_hotdog(u8 threshold) +{ + return 0; +} + +static inline int prcmu_config_hotmon(u8 low, u8 high) +{ + return 0; +} + +static inline int prcmu_start_temp_sense(u16 cycles32k) +{ + return 0; +} + +static inline int prcmu_stop_temp_sense(void) +{ + return 0; +} + +#endif + +/* PRCMU QoS APE OPP class */ +#define PRCMU_QOS_APE_OPP 1 +#define PRCMU_QOS_DDR_OPP 2 +#define PRCMU_QOS_ARM_OPP 3 +#define PRCMU_QOS_DEFAULT_VALUE -1 + +#ifdef CONFIG_DBX500_PRCMU_QOS_POWER + +unsigned long prcmu_qos_get_cpufreq_opp_delay(void); +void prcmu_qos_set_cpufreq_opp_delay(unsigned long); +void prcmu_qos_force_opp(int, s32); +int prcmu_qos_requirement(int pm_qos_class); +int prcmu_qos_add_requirement(int pm_qos_class, char *name, s32 value); +int prcmu_qos_update_requirement(int pm_qos_class, char *name, s32 new_value); +void prcmu_qos_remove_requirement(int pm_qos_class, char *name); +int prcmu_qos_add_notifier(int prcmu_qos_class, + struct notifier_block *notifier); +int prcmu_qos_remove_notifier(int prcmu_qos_class, + struct notifier_block *notifier); +void prcmu_qos_voice_call_override(bool enable); + +#else + +static inline unsigned long prcmu_qos_get_cpufreq_opp_delay(void) +{ + return 0; +} + +static inline void prcmu_qos_set_cpufreq_opp_delay(unsigned long n) {} + +static inline void prcmu_qos_force_opp(int prcmu_qos_class, s32 i) {} + +static inline int prcmu_qos_requirement(int prcmu_qos_class) +{ + return 0; +} + +static inline int prcmu_qos_add_requirement(int prcmu_qos_class, + char *name, s32 value) +{ + return 0; +} + +static inline int prcmu_qos_update_requirement(int prcmu_qos_class, + char *name, s32 new_value) +{ + return 0; +} + +static inline void prcmu_qos_remove_requirement(int prcmu_qos_class, char *name) +{ +} + +static inline int prcmu_qos_add_notifier(int prcmu_qos_class, + struct notifier_block *notifier) +{ + return 0; +} +static inline int prcmu_qos_remove_notifier(int prcmu_qos_class, + struct notifier_block *notifier) +{ + return 0; +} +static inline void prcmu_qos_voice_call_override(bool enable) {} +#endif + +#endif /* __MACH_PRCMU_H */ diff --git a/include/linux/mfd/stmpe.h b/include/linux/mfd/stmpe.h index be1af7c42e5..54046d1d6b5 100644 --- a/include/linux/mfd/stmpe.h +++ b/include/linux/mfd/stmpe.h @@ -107,7 +107,7 @@ struct matrix_keymap_data; * @no_autorepeat: disable key autorepeat */ struct stmpe_keypad_platform_data { - struct matrix_keymap_data *keymap_data; + const struct matrix_keymap_data *keymap_data; unsigned int debounce_ms; unsigned int scan_count; bool no_autorepeat; diff --git a/include/linux/mfd/tc35892.h b/include/linux/mfd/tc35892.h new file mode 100644 index 00000000000..8c5385c2191 --- /dev/null +++ b/include/linux/mfd/tc35892.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + */ + +#ifndef __LINUX_MFD_TC35892_H +#define __LINUX_MFD_TC35892_H + +#include <linux/device.h> + +#define TC35892_RSTCTRL_IRQRST (1 << 4) +#define TC35892_RSTCTRL_TIMRST (1 << 3) +#define TC35892_RSTCTRL_ROTRST (1 << 2) +#define TC35892_RSTCTRL_KBDRST (1 << 1) +#define TC35892_RSTCTRL_GPIRST (1 << 0) + + +#define TC35892_MANFCODE 0x80 +#define TC35892_MANFCODE_MAGIC 0x03 +#define TC35892_VERSION 0x81 +#define TC35892_RSTCTRL 0x82 +#define TC35892_EXTRSTN 0x83 +#define TC35892_RSTINTCLR 0x84 +#define TC35892_CLKMODE 0x88 +#define TC35892_CLKCFG 0x89 +#define TC35892_CLKEN 0x8A +#define TC35892_IRQST 0x91 + +#define TC35892_DRIVE0_L 0xA0 +#define TC35892_DRIVE0_H 0xA1 +#define TC35892_DRIVE1_L 0xA2 +#define TC35892_DRIVE1_H 0xA3 +#define TC35892_DRIVE2_L 0xA4 +#define TC35892_DRIVE2_H 0XA5 +#define TC35892_DRIVE3 0xA6 +#define TC35892_IOCFG 0xA7 + +#define TC35892_IOPC0_L 0xAA +#define TC35892_IOPC0_H 0xAB +#define TC35892_IOPC1_L 0xAC +#define TC35892_IOPC1_H 0xAD +#define TC35892_IOPC2_L 0xAE +#define TC35892_IOPC2_H 0xAF + +#define TC35892_GPIODATA0 0xC0 +#define TC35892_GPIOMASK0 0xC1 +#define TC35892_GPIODATA1 0xC2 +#define TC35892_GPIOMASK1 0xC3 +#define TC35892_GPIODATA2 0xC4 +#define TC35892_GPIOMASK2 0xC5 +#define TC35892_GPIODIR0 0xC6 +#define TC35892_GPIODIR1 0xC7 +#define TC35892_GPIODIR2 0xC8 +#define TC35892_GPIOIS0 0xC9 +#define TC35892_GPIOIS1 0xCA +#define TC35892_GPIOIS2 0xCB +#define TC35892_GPIOIBE0 0xCC +#define TC35892_GPIOIBE1 0xCD +#define TC35892_GPIOIBE2 0xCE +#define TC35892_GPIOIEV0 0xCF +#define TC35892_GPIOIEV1 0xD0 +#define TC35892_GPIOIEV2 0xD1 +#define TC35892_GPIOIE0 0xD2 +#define TC35892_GPIOIE1 0xD3 +#define TC35892_GPIOIE2 0xD4 +#define TC35892_GPIORIS0 0xD6 +#define TC35892_GPIORIS1 0xD7 +#define TC35892_GPIORIS2 0xD8 +#define TC35892_GPIOMIS0 0xD9 +#define TC35892_GPIOMIS1 0xDA +#define TC35892_GPIOMIS2 0xDB +#define TC35892_GPIOIC0 0xDC +#define TC35892_GPIOIC1 0xDD +#define TC35892_GPIOIC2 0xDE +#define TC35892_GPIOODM0 0xE0 +#define TC35892_GPIOODE0 0xE1 +#define TC35892_GPIOODM1 0xE2 +#define TC35892_GPIOODE1 0xE3 +#define TC35892_GPIOODM2 0xE4 +#define TC35892_GPIOODE2 0xE5 + +#define TC35892_GPIOSYNC0 0xE6 +#define TC35892_GPIOSYNC1 0xE7 +#define TC35892_GPIOSYNC2 0xE8 + +#define TC35892_GPIOWAKE0 0xE9 +#define TC35892_GPIOWAKE1 0xEA +#define TC35892_GPIOWAKE2 0xEB + +#define TC35892_INT_GPIIRQ 0 +#define TC35892_INT_TI0IRQ 1 +#define TC35892_INT_TI1IRQ 2 +#define TC35892_INT_TI2IRQ 3 +#define TC35892_INT_ROTIRQ 5 +#define TC35892_INT_KBDIRQ 6 +#define TC35892_INT_PORIRQ 7 + +#define TC35892_NR_INTERNAL_IRQS 8 +#define TC35892_INT_GPIO(x) (TC35892_NR_INTERNAL_IRQS + (x)) + +struct tc35892 { + struct mutex lock; + struct device *dev; + struct i2c_client *i2c; + + int irq_base; + int num_gpio; + struct tc35892_platform_data *pdata; +}; + +extern int tc35892_reg_write(struct tc35892 *tc35892, u8 reg, u8 data); +extern int tc35892_reg_read(struct tc35892 *tc35892, u8 reg); +extern int tc35892_block_read(struct tc35892 *tc35892, u8 reg, u8 length, + u8 *values); +extern int tc35892_block_write(struct tc35892 *tc35892, u8 reg, u8 length, + const u8 *values); +extern int tc35892_set_bits(struct tc35892 *tc35892, u8 reg, u8 mask, u8 val); + +/** + * struct tc35892_gpio_platform_data - TC35892 GPIO platform data + * @gpio_base: first gpio number assigned to TC35892. A maximum of + * %TC35892_NR_GPIOS GPIOs will be allocated. + * @setup: callback for board-specific initialization + * @remove: callback for board-specific teardown + */ +struct tc35892_gpio_platform_data { + int gpio_base; + void (*setup)(struct tc35892 *tc35892, unsigned gpio_base); + void (*remove)(struct tc35892 *tc35892, unsigned gpio_base); +}; + +/** + * struct tc35892_platform_data - TC35892 platform data + * @irq_base: base IRQ number. %TC35892_NR_IRQS irqs will be used. + * @gpio: GPIO-specific platform data + */ +struct tc35892_platform_data { + int irq_base; + struct tc35892_gpio_platform_data *gpio; +}; + +#define TC35892_NR_GPIOS 24 +#define TC35892_NR_IRQS TC35892_INT_GPIO(TC35892_NR_GPIOS) + +#endif diff --git a/include/linux/mfd/tc3589x.h b/include/linux/mfd/tc3589x.h index 16c76e124f9..b8c6a941071 100644 --- a/include/linux/mfd/tc3589x.h +++ b/include/linux/mfd/tc3589x.h @@ -31,20 +31,43 @@ enum tx3589x_block { #define TC3589x_EVTCODE_FIFO 0x10 #define TC3589x_KBDMFS 0x8F -#define TC3589x_IRQST 0x91 - -#define TC3589x_MANFCODE_MAGIC 0x03 #define TC3589x_MANFCODE 0x80 +#define TC3589x_MANFCODE_MAGIC 0x03 #define TC3589x_VERSION 0x81 -#define TC3589x_IOCFG 0xA7 +#define TC3589x_RSTCTRL 0x82 +#define TC3589x_EXTRSTN 0x83 +#define TC3589x_RSTINTCLR 0x84 #define TC3589x_CLKMODE 0x88 #define TC3589x_CLKCFG 0x89 #define TC3589x_CLKEN 0x8A +#define TC3589x_IRQST 0x91 -#define TC3589x_RSTCTRL 0x82 -#define TC3589x_EXTRSTN 0x83 -#define TC3589x_RSTINTCLR 0x84 +#define TC3589x_DRIVE0_L 0xA0 +#define TC3589x_DRIVE0_H 0xA1 +#define TC3589x_DRIVE1_L 0xA2 +#define TC3589x_DRIVE1_H 0xA3 +#define TC3589x_DRIVE2_L 0xA4 +#define TC3589x_DRIVE2_H 0XA5 +#define TC3589x_DRIVE3 0xA6 +#define TC3589x_IOCFG 0xA7 + +#define TC3589x_IOPC0_L 0xAA +#define TC3589x_IOPC0_H 0xAB +#define TC3589x_IOPC1_L 0xAC +#define TC3589x_IOPC1_H 0xAD +#define TC3589x_IOPC2_L 0xAE +#define TC3589x_IOPC2_H 0xAF + +#define TC3589x_GPIODATA0 0xC0 +#define TC3589x_GPIOMASK0 0xC1 +#define TC3589x_GPIODATA1 0xC2 +#define TC3589x_GPIOMASK1 0xC3 +#define TC3589x_GPIODATA2 0xC4 +#define TC3589x_GPIOMASK2 0xC5 +#define TC3589x_GPIODIR0 0xC6 +#define TC3589x_GPIODIR1 0xC7 +#define TC3589x_GPIODIR2 0xC8 /* Pull up/down configuration registers */ #define TC3589x_IOCFG 0xA7 @@ -75,17 +98,12 @@ enum tx3589x_block { #define TC3589x_GPIOIC0 0xDC #define TC3589x_GPIOIC1 0xDD #define TC3589x_GPIOIC2 0xDE - -#define TC3589x_GPIODATA0 0xC0 -#define TC3589x_GPIOMASK0 0xc1 -#define TC3589x_GPIODATA1 0xC2 -#define TC3589x_GPIOMASK1 0xc3 -#define TC3589x_GPIODATA2 0xC4 -#define TC3589x_GPIOMASK2 0xC5 - -#define TC3589x_GPIODIR0 0xC6 -#define TC3589x_GPIODIR1 0xC7 -#define TC3589x_GPIODIR2 0xC8 +#define TC3589x_GPIOODM0 0xE0 +#define TC3589x_GPIOODE0 0xE1 +#define TC3589x_GPIOODM1 0xE2 +#define TC3589x_GPIOODE1 0xE3 +#define TC3589x_GPIOODM2 0xE4 +#define TC3589x_GPIOODE2 0xE5 #define TC3589x_GPIOSYNC0 0xE6 #define TC3589x_GPIOSYNC1 0xE7 @@ -95,13 +113,6 @@ enum tx3589x_block { #define TC3589x_GPIOWAKE1 0xEA #define TC3589x_GPIOWAKE2 0xEB -#define TC3589x_GPIOODM0 0xE0 -#define TC3589x_GPIOODE0 0xE1 -#define TC3589x_GPIOODM1 0xE2 -#define TC3589x_GPIOODE1 0xE3 -#define TC3589x_GPIOODM2 0xE4 -#define TC3589x_GPIOODE2 0xE5 - #define TC3589x_INT_GPIIRQ 0 #define TC3589x_INT_TI0IRQ 1 #define TC3589x_INT_TI1IRQ 2 diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index b460fc2af8a..dc20c5b6e5f 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -12,6 +12,7 @@ #include <linux/mmc/core.h> #include <linux/mod_devicetable.h> +#include <linux/genhd.h> struct mmc_cid { unsigned int manfid; @@ -50,8 +51,10 @@ struct mmc_ext_csd { u8 rel_sectors; u8 rel_param; u8 part_config; + u8 rst_n_function; unsigned int part_time; /* Units: ms */ unsigned int sa_timeout; /* Units: 100ns */ + unsigned int generic_cmd6_time; /* Units: 10ms */ unsigned int hs_max_dtr; unsigned int sectors; unsigned int card_type; @@ -63,7 +66,7 @@ struct mmc_ext_csd { bool enhanced_area_en; /* enable bit */ unsigned long long enhanced_area_offset; /* Units: Byte */ unsigned int enhanced_area_size; /* Units: KB */ - unsigned int boot_size; /* in bytes */ + unsigned int boot_locked; u8 raw_partition_support; /* 160 */ u8 raw_erased_mem_count; /* 181 */ u8 raw_ext_csd_structure; /* 194 */ @@ -157,6 +160,32 @@ struct sdio_func_tuple; #define SDIO_MAX_FUNCS 7 +/* The number of MMC physical partitions. These consist of: + * boot partitions (2), general purpose partitions (4) in MMC v4.4. + */ +#define MMC_NUM_BOOT_PARTITION 2 +#define MMC_NUM_GP_PARTITION 4 +#define MMC_NUM_PHY_PARTITION 6 + +/* + * Partition area type, boot or gp + */ +enum mmc_part_area_type { + MMC_BLK_DATA_AREA_BOOT, + MMC_BLK_DATA_AREA_GP, +}; + +/* + * MMC Physical partitions + */ +struct mmc_part { + unsigned int size; /* partition size (in bytes) */ + unsigned int part_cfg; /* partition type */ + char name[DISK_NAME_LEN]; + bool force_ro; /* to make boot parts RO by default */ + int area_type; +}; + /* * MMC device */ @@ -188,6 +217,7 @@ struct mmc_card { #define MMC_QUIRK_DISABLE_CD (1<<5) /* disconnect CD/DAT[3] resistor */ #define MMC_QUIRK_INAND_CMD38 (1<<6) /* iNAND devices have broken CMD38 */ #define MMC_QUIRK_BLK_NO_CMD23 (1<<7) /* Avoid CMD23 for regular multiblock */ +#define MMC_QUIRK_BROKEN_BYTE_MODE_512 (1<<8) /* Avoid sending 512 bytes in byte mode */ unsigned int erase_size; /* erase size in sectors */ unsigned int erase_shift; /* if erase unit is power 2 */ @@ -216,9 +246,26 @@ struct mmc_card { unsigned int sd_bus_speed; /* Bus Speed Mode set for the card */ struct dentry *debugfs_root; + struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */ + unsigned int nr_parts; }; /* + * This function fill contents in mmc_part. + */ +static inline void mmc_part_add(struct mmc_card *card, unsigned int size, + unsigned int part_cfg, char *name, int idx, bool ro, + int area_type) +{ + card->part[card->nr_parts].size = size; + card->part[card->nr_parts].part_cfg = part_cfg; + sprintf(card->part[card->nr_parts].name, name, idx); + card->part[card->nr_parts].force_ro = ro; + card->part[card->nr_parts].area_type = area_type; + card->nr_parts++; +} + +/* * The world is not perfect and supplies us with broken mmc/sdio devices. * For at least some of these bugs we need a work-around. */ @@ -377,6 +424,11 @@ static inline int mmc_card_nonstd_func_interface(const struct mmc_card *c) return c->quirks & MMC_QUIRK_NONSTD_FUNC_IF; } +static inline int mmc_card_broken_byte_mode_512(const struct mmc_card *c) +{ + return c->quirks & MMC_QUIRK_BROKEN_BYTE_MODE_512; +} + #define mmc_card_name(c) ((c)->cid.prod_name) #define mmc_card_id(c) (dev_name(&(c)->dev)) diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index b8b1b7a311f..56e5625b6f4 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -162,6 +162,9 @@ extern int mmc_erase_group_aligned(struct mmc_card *card, unsigned int from, extern unsigned int mmc_calc_max_discard(struct mmc_card *card); extern int mmc_set_blocklen(struct mmc_card *card, unsigned int blocklen); +extern int mmc_hw_reset(struct mmc_host *host); +extern int mmc_hw_reset_check(struct mmc_host *host); +extern int mmc_can_reset(struct mmc_card *card); extern void mmc_set_data_timeout(struct mmc_data *, const struct mmc_card *); extern unsigned int mmc_align_data_size(struct mmc_card *, unsigned int); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 1d09562ccf7..79117a252f0 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -12,6 +12,7 @@ #include <linux/leds.h> #include <linux/sched.h> +#include <linux/fault-inject.h> #include <linux/mmc/core.h> #include <linux/mmc/pm.h> @@ -108,6 +109,9 @@ struct mmc_host_ops { * It is optional for the host to implement pre_req and post_req in * order to support double buffering of requests (prepare one * request while another request is active). + * pre_req() must always be followed by a post_req(). + * To undo a call made to pre_req(), call post_req() with + * a nonzero err condition. */ void (*post_req)(struct mmc_host *host, struct mmc_request *req, int err); @@ -147,6 +151,7 @@ struct mmc_host_ops { int (*execute_tuning)(struct mmc_host *host); void (*enable_preset_value)(struct mmc_host *host, bool enable); int (*select_drive_strength)(unsigned int max_dtr, int host_drv, int card_drv); + void (*hw_reset)(struct mmc_host *host); }; struct mmc_card; @@ -229,6 +234,11 @@ struct mmc_host { #define MMC_CAP_MAX_CURRENT_600 (1 << 28) /* Host max current limit is 600mA */ #define MMC_CAP_MAX_CURRENT_800 (1 << 29) /* Host max current limit is 800mA */ #define MMC_CAP_CMD23 (1 << 30) /* CMD23 supported. */ +#define MMC_CAP_HW_RESET (1 << 31) /* Hardware reset */ + + unsigned int caps2; /* More host capabilities */ + +#define MMC_CAP2_BOOTPART_NOACC (1 << 0) /* Boot partition no access */ mmc_pm_flag_t pm_caps; /* supported pm features */ @@ -302,6 +312,19 @@ struct mmc_host { struct mmc_async_req *areq; /* active async req */ +#ifdef CONFIG_FAIL_MMC_REQUEST + struct fault_attr fail_mmc_request; +#endif + +#ifdef CONFIG_MMC_EMBEDDED_SDIO + struct { + struct sdio_cis *cis; + struct sdio_cccr *cccr; + struct sdio_embedded_func *funcs; + int num_funcs; + } embedded_sdio_data; +#endif + unsigned long private[0] ____cacheline_aligned; }; @@ -394,4 +417,10 @@ static inline int mmc_host_cmd23(struct mmc_host *host) { return host->caps & MMC_CAP_CMD23; } + +static inline int mmc_boot_partition_access(struct mmc_host *host) +{ + return !(host->caps2 & MMC_CAP2_BOOTPART_NOACC); +} + #endif /* LINUX_MMC_HOST_H */ diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 5a794cb503e..dc884f60798 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -270,18 +270,26 @@ struct _mmc_csd { * EXT_CSD fields */ +#define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ #define EXT_CSD_PARTITION_SUPPORT 160 /* RO */ +#define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ #define EXT_CSD_WR_REL_PARAM 166 /* RO */ +#define EXT_CSD_BOOT_WP 173 /* R/W */ #define EXT_CSD_ERASE_GROUP_DEF 175 /* R/W */ #define EXT_CSD_PART_CONFIG 179 /* R/W */ #define EXT_CSD_ERASED_MEM_CONT 181 /* RO */ #define EXT_CSD_BUS_WIDTH 183 /* R/W */ #define EXT_CSD_HS_TIMING 185 /* R/W */ +#define EXT_CSD_POWER_CLASS 187 /* R/W */ #define EXT_CSD_REV 192 /* RO */ #define EXT_CSD_STRUCTURE 194 /* RO */ #define EXT_CSD_CARD_TYPE 196 /* RO */ #define EXT_CSD_PART_SWITCH_TIME 199 /* RO */ +#define EXT_CSD_PWR_CL_52_195 200 /* RO */ +#define EXT_CSD_PWR_CL_26_195 201 /* RO */ +#define EXT_CSD_PWR_CL_52_360 202 /* RO */ +#define EXT_CSD_PWR_CL_26_360 203 /* RO */ #define EXT_CSD_SEC_CNT 212 /* RO, 4 bytes */ #define EXT_CSD_S_A_TIMEOUT 217 /* RO */ #define EXT_CSD_REL_WR_SEC_C 222 /* RO */ @@ -293,6 +301,12 @@ struct _mmc_csd { #define EXT_CSD_SEC_ERASE_MULT 230 /* RO */ #define EXT_CSD_SEC_FEATURE_SUPPORT 231 /* RO */ #define EXT_CSD_TRIM_MULT 232 /* RO */ +#define EXT_CSD_PWR_CL_200_195 236 /* RO */ +#define EXT_CSD_PWR_CL_200_360 237 /* RO */ +#define EXT_CSD_PWR_CL_DDR_52_195 238 /* RO */ +#define EXT_CSD_PWR_CL_DDR_52_360 239 /* RO */ +#define EXT_CSD_POWER_OFF_LONG_TIME 247 /* RO */ +#define EXT_CSD_GENERIC_CMD6_TIME 248 /* RO */ /* * EXT_CSD field definitions @@ -300,9 +314,16 @@ struct _mmc_csd { #define EXT_CSD_WR_REL_PARAM_EN (1<<2) +#define EXT_CSD_BOOT_WP_B_PWR_WP_DIS (0x40) +#define EXT_CSD_BOOT_WP_B_PERM_WP_DIS (0x10) +#define EXT_CSD_BOOT_WP_B_PERM_WP_EN (0x04) +#define EXT_CSD_BOOT_WP_B_PWR_WP_EN (0x01) + #define EXT_CSD_PART_CONFIG_ACC_MASK (0x7) #define EXT_CSD_PART_CONFIG_ACC_BOOT0 (0x1) -#define EXT_CSD_PART_CONFIG_ACC_BOOT1 (0x2) +#define EXT_CSD_PART_CONFIG_ACC_GP0 (0x4) + +#define EXT_CSD_PART_SUPPORT_PART_EN (0x1) #define EXT_CSD_CMD_SET_NORMAL (1<<0) #define EXT_CSD_CMD_SET_SECURE (1<<1) @@ -328,6 +349,13 @@ struct _mmc_csd { #define EXT_CSD_SEC_BD_BLK_EN BIT(2) #define EXT_CSD_SEC_GB_CL_EN BIT(4) +#define EXT_CSD_RST_N_EN_MASK 0x3 +#define EXT_CSD_RST_N_ENABLED 1 /* RST_n is enabled on card */ + +#define EXT_CSD_PWR_CL_8BIT_MASK 0xF0 /* 8 bit PWR CLS */ +#define EXT_CSD_PWR_CL_4BIT_MASK 0x0F /* 8 bit PWR CLS */ +#define EXT_CSD_PWR_CL_8BIT_SHIFT 4 +#define EXT_CSD_PWR_CL_4BIT_SHIFT 0 /* * MMC_SWITCH access modes */ diff --git a/include/linux/regulator/ab5500.h b/include/linux/regulator/ab5500.h new file mode 100644 index 00000000000..2d85b57cae8 --- /dev/null +++ b/include/linux/regulator/ab5500.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + */ + +#ifndef __LINUX_REGULATOR_AB5500_H +#define __LINUX_REGULATOR_AB5500_H + +enum ab5500_regulator_id { + AB5500_LDO_G, + AB5500_LDO_H, + AB5500_LDO_K, + AB5500_LDO_L, + AB5500_LDO_VDIGMIC, + AB5500_LDO_SIM, + AB5500_BIAS2, + AB5500_NUM_REGULATORS, +}; + +struct regulator_init_data; + +struct ab5500_regulator_platform_data { + struct regulator_init_data *regulator; + int num_regulator; +}; + +#endif diff --git a/include/linux/regulator/ab8500-debug.h b/include/linux/regulator/ab8500-debug.h new file mode 100644 index 00000000000..01655fc7fc1 --- /dev/null +++ b/include/linux/regulator/ab8500-debug.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * + * Authors: Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson + */ + +#ifndef __LINUX_MFD_AB8500_REGULATOR_DEBUG_H +#define __LINUX_MFD_AB8500_REGULATOR_DEBUG_H + +#ifdef CONFIG_REGULATOR_AB8500_DEBUG +/* AB8500 debug force/restore functions */ +void ab8500_regulator_debug_force(void); +void ab8500_regulator_debug_restore(void); +#else +static inline void ab8500_regulator_debug_force(void) {} +static inline void ab8500_regulator_debug_restore(void) {} +#endif + +#endif diff --git a/include/linux/regulator/ab8500.h b/include/linux/regulator/ab8500.h index 76579f964a2..df1e148da73 100644 --- a/include/linux/regulator/ab8500.h +++ b/include/linux/regulator/ab8500.h @@ -10,6 +10,8 @@ #ifndef __LINUX_MFD_AB8500_REGULATOR_H #define __LINUX_MFD_AB8500_REGULATOR_H +#include <linux/platform_device.h> + /* AB8500 regulators */ enum ab8500_regulator_id { AB8500_LDO_AUX1, @@ -23,23 +25,28 @@ enum ab8500_regulator_id { AB8500_LDO_ANAMIC2, AB8500_LDO_DMIC, AB8500_LDO_ANA, + AB8500_SYSCLKREQ_2, + AB8500_SYSCLKREQ_4, AB8500_NUM_REGULATORS, }; /* AB8500 register initialization */ struct ab8500_regulator_reg_init { int id; + u8 mask; u8 value; }; -#define INIT_REGULATOR_REGISTER(_id, _value) \ - { \ - .id = _id, \ - .value = _value, \ +#define INIT_REGULATOR_REGISTER(_id, _mask, _value) \ + { \ + .id = _id, \ + .mask = _mask, \ + .value = _value, \ } /* AB8500 registers */ enum ab8500_regulator_reg { + AB8500_REGUREQUESTCTRL1, AB8500_REGUREQUESTCTRL2, AB8500_REGUREQUESTCTRL3, AB8500_REGUREQUESTCTRL4, @@ -56,19 +63,52 @@ enum ab8500_regulator_reg { AB8500_REGUMISC1, AB8500_VAUDIOSUPPLY, AB8500_REGUCTRL1VAMIC, + AB8500_VSMPS1REGU, + AB8500_VSMPS2REGU, + AB8500_VSMPS3REGU, /* NOTE! PRCMU register */ AB8500_VPLLVANAREGU, AB8500_VREFDDR, AB8500_EXTSUPPLYREGU, AB8500_VAUX12REGU, AB8500_VRF1VAUX3REGU, + AB8500_VSMPS1SEL1, + AB8500_VSMPS1SEL2, + AB8500_VSMPS1SEL3, + AB8500_VSMPS2SEL1, + AB8500_VSMPS2SEL2, + AB8500_VSMPS2SEL3, + AB8500_VSMPS3SEL1, /* NOTE! PRCMU register */ + AB8500_VSMPS3SEL2, /* NOTE! PRCMU register */ AB8500_VAUX1SEL, AB8500_VAUX2SEL, AB8500_VRF1VAUX3SEL, AB8500_REGUCTRL2SPARE, AB8500_REGUCTRLDISCH, AB8500_REGUCTRLDISCH2, - AB8500_VSMPS1SEL1, AB8500_NUM_REGULATOR_REGISTERS, }; +/* AB8500 external regulators */ +enum ab8500_ext_regulator_id { + AB8500_EXT_SUPPLY3, + AB8500_NUM_EXT_REGULATORS, +}; + +struct ab8500_regulator_platform_data { + int num_reg_init; + struct ab8500_regulator_reg_init *reg_init; + int num_regulator; + struct regulator_init_data *regulator; + int num_ext_regulator; + struct regulator_init_data *ext_regulator; +}; + +#ifdef CONFIG_REGULATOR_AB8500_EXT +__devinit int ab8500_ext_regulator_init(struct platform_device *pdev); +__devexit int ab8500_ext_regulator_exit(struct platform_device *pdev); +#else +inline __devinit int ab8500_ext_regulator_init(struct platform_device *pdev) {} +inline __devexit int ab8500_ext_regulator_exit(struct platform_device *pdev) {} +#endif + #endif diff --git a/include/linux/regulator/db5500-prcmu.h b/include/linux/regulator/db5500-prcmu.h new file mode 100644 index 00000000000..fee68795867 --- /dev/null +++ b/include/linux/regulator/db5500-prcmu.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson + * + * Interface to power domain regulators on DB5500 + */ + +#ifndef __DB5500_REGULATOR_H__ +#define __DB5500_REGULATOR_H__ + +#include <linux/regulator/dbx500-prcmu.h> + +/* Number of DB5500 regulators and regulator enumeration */ +enum db5500_regulator_id { + DB5500_REGULATOR_VAPE, + DB5500_REGULATOR_SWITCH_SGA, + DB5500_REGULATOR_SWITCH_HVA, + DB5500_REGULATOR_SWITCH_SIA, + DB5500_REGULATOR_SWITCH_DISP, + DB5500_REGULATOR_SWITCH_ESRAM12, + DB5500_NUM_REGULATORS +}; + +#endif diff --git a/include/linux/regulator/dbx500-prcmu.h b/include/linux/regulator/dbx500-prcmu.h new file mode 100644 index 00000000000..2ecb34c56aa --- /dev/null +++ b/include/linux/regulator/dbx500-prcmu.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) ST Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * + */ +#ifndef __LINUX_REGULATOR_DBX500_H +#define __LINUX_REGULATOR_DBX500_H + +struct ux500_regulator; + +#ifdef CONFIG_REGULATOR +/* + * NOTE! The device will be connected to the correct regulator by this + * new framework. A list with connections will match up dev_name(dev) + * to the specific regulator. This follows the same principle as the + * normal regulator framework. + * + * This framework shall only be used in special cases when a regulator + * has to be enabled/disabled in atomic context. + */ + +/** + * ux500_regulator_get() + * + * @dev: Drivers device struct + * + * Returns a ux500_regulator struct. Shall be used as argument for + * ux500_regulator_atomic_enable/disable calls. + * Return ERR_PTR(-EINVAL) upon no matching regulator found. + */ +struct ux500_regulator *__must_check ux500_regulator_get(struct device *dev); + +/** + * ux500_regulator_atomic_enable() + * + * @regulator: Regulator handle, provided from ux500_regulator_get. + * + * The enable/disable functions keep an internal counter, so every + * enable must be paired with an disable in order to turn off regulator. + */ +int ux500_regulator_atomic_enable(struct ux500_regulator *regulator); + +/** + * ux500_regulator_atomic_disable() + * + * @regulator: Regulator handle, provided from ux500_regulator_get. + * + */ +int ux500_regulator_atomic_disable(struct ux500_regulator *regulator); + +/** + * ux500_regulator_put() + * + * @regulator: Regulator handle, provided from ux500_regulator_get. + */ +void ux500_regulator_put(struct ux500_regulator *regulator); + +#else + +static inline struct ux500_regulator *__must_check +ux500_regulator_get(struct device *dev) +{ + return ERR_PTR(-EINVAL); +} + +static inline int +ux500_regulator_atomic_enable(struct ux500_regulator *regulator) +{ + return -EINVAL; +} + +static inline int +ux500_regulator_atomic_disable(struct ux500_regulator *regulator) +{ + return -EINVAL; +} + +static inline void ux500_regulator_put(struct ux500_regulator *regulator) +{ +} +#endif /* CONFIG_REGULATOR */ + +#ifdef CONFIG_REGULATOR_DEBUG +void ux500_regulator_suspend_debug(void); +void ux500_regulator_resume_debug(void); +#else +static inline void ux500_regulator_suspend_debug(void) { } +static inline void ux500_regulator_resume_debug(void) { } +#endif + +#endif diff --git a/include/linux/sys_soc.h b/include/linux/sys_soc.h new file mode 100644 index 00000000000..05e5529a6aa --- /dev/null +++ b/include/linux/sys_soc.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Maxime Coquelin <maxime.coquelin-nonst@stericsson.com> for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ +#ifndef __SYS_SOC_H +#define __SYS_SOC_H + +#include <linux/kobject.h> + +/** + * struct sys_soc_info - SoC exports related informations + * @name: name of the export + * @info: pointer on the key to export + * @get_info: callback to retrieve key if info field is NULL + * @attr: export's sysdev class attribute + */ +struct sysfs_soc_info { + const char *info; + ssize_t (*get_info)(char *buf, struct sysfs_soc_info *); + struct kobj_attribute attr; +}; + +ssize_t show_soc_info(struct kobject *, struct kobj_attribute *, char *); + +#define SYSFS_SOC_ATTR_VALUE(_name, _value) { \ + .attr.attr.name = _name, \ + .attr.attr.mode = S_IRUGO, \ + .attr.show = show_soc_info, \ + .info = _value, \ +} + +#define SYSFS_SOC_ATTR_CALLBACK(_name, _callback) { \ + .attr.attr.name = _name, \ + .attr.attr.mode = S_IRUGO, \ + .attr.show = show_soc_info, \ + .get_info = _callback, \ +} + +/** + * register_sys_soc - register the soc information + * @name: name of the machine + * @info: pointer on the info table to export + * @num: number of info to export + * + * NOTE: This function must only be called once + */ +int register_sysfs_soc(struct sysfs_soc_info *info, size_t num); + +#endif /* __SYS_SOC_H */ diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index dc5114b4c16..383101aaa92 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -244,7 +244,7 @@ void handle_nested_irq(unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); struct irqaction *action; - irqreturn_t action_ret; + irqreturn_t action_ret = IRQ_NONE; might_sleep(); @@ -259,7 +259,11 @@ void handle_nested_irq(unsigned int irq) irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); raw_spin_unlock_irq(&desc->lock); - action_ret = action->thread_fn(action->irq, action->dev_id); + do { + action_ret |= action->thread_fn(action->irq, action->dev_id); + action = action->next; + } while (action); + if (!noirqdebug) note_interrupt(irq, desc, action_ret); diff --git a/kernel/kexec.c b/kernel/kexec.c index 296fbc84d65..1d6121de24f 100644 --- a/kernel/kexec.c +++ b/kernel/kexec.c @@ -50,6 +50,8 @@ u32 vmcoreinfo_note[VMCOREINFO_NOTE_SIZE/4]; size_t vmcoreinfo_size; size_t vmcoreinfo_max_size = sizeof(vmcoreinfo_data); +ATOMIC_NOTIFIER_HEAD(crash_percpu_notifier_list); + /* Location of the reserved area for the crash kernel */ struct resource crashk_res = { .name = "Crash kernel", @@ -1067,6 +1069,7 @@ asmlinkage long compat_sys_kexec_load(unsigned long entry, void crash_kexec(struct pt_regs *regs) { + struct pt_regs fixed_regs; /* Take the kexec_mutex here to prevent sys_kexec_load * running on one cpu from replacing the crash kernel * we are using after a panic on a different cpu. @@ -1077,7 +1080,6 @@ void crash_kexec(struct pt_regs *regs) */ if (mutex_trylock(&kexec_mutex)) { if (kexec_crash_image) { - struct pt_regs fixed_regs; kmsg_dump(KMSG_DUMP_KEXEC); @@ -1086,6 +1088,14 @@ void crash_kexec(struct pt_regs *regs) machine_crash_shutdown(&fixed_regs); machine_kexec(kexec_crash_image); } +#ifdef CONFIG_CRASH_SWRESET + else { + crash_setup_regs(&fixed_regs, regs); + crash_save_vmcoreinfo(); + machine_crash_shutdown(&fixed_regs); + machine_crash_swreset(); + } +#endif mutex_unlock(&kexec_mutex); } } diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index c0cb9c4bc46..1c7dbbf9e44 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1070,6 +1070,17 @@ config FAIL_IO_TIMEOUT Only works with drivers that use the generic timeout handling, for others it wont do anything. +config FAIL_MMC_REQUEST + bool "Fault-injection capability for MMC IO" + select DEBUG_FS + depends on FAULT_INJECTION && MMC + help + Provide fault-injection capability for MMC IO. + This will make the mmc core return data errors. This is + useful to test the error handling in the mmc block device + and to test how the mmc host driver handles retries from + the block device. + config FAULT_INJECTION_DEBUG_FS bool "Debugfs entries for fault-injection capabilities" depends on FAULT_INJECTION && SYSFS && DEBUG_FS diff --git a/scripts/setlocalversion b/scripts/setlocalversion index 4d403844e13..06c33adfe7f 100755 --- a/scripts/setlocalversion +++ b/scripts/setlocalversion @@ -10,23 +10,37 @@ # usage() { - echo "Usage: $0 [--save-scmversion] [srctree]" >&2 + echo "Usage: $0 [--save-scmversion] [-s srctree] [-t ref_tag]" >&2 exit 1 } scm_only=false srctree=. -if test "$1" = "--save-scmversion"; then - scm_only=true - shift -fi -if test $# -gt 0; then - srctree=$1 +match_option=--exact-match + +while [ $# -ne 0 ]; do + if test "$1" = "--save-scmversion"; then + scm_only=true + elif test "$1" = "-s"; then + shift + if test $# -ne 0 -a -d "$1"; then + srctree=$1 + else + usage + fi + elif test "$1" = "-t"; then + shift + if [ $# -ne 0 ]; then + match=" --tags --match "$1 + rev_refs="--refs refs/tags/"$1 + else + usage + fi + else + usage + fi shift -fi -if test $# -gt 0 -o ! -d "$srctree"; then - usage -fi +done scm_version() { @@ -47,8 +61,8 @@ scm_version() # If we are at a tagged commit (like "v2.6.30-rc6"), we ignore # it, because this version is defined in the top level Makefile. - if [ -z "`git describe --exact-match 2>/dev/null`" ]; then - + if git name-rev --tags $rev_refs HEAD | \ + grep -E '^HEAD[[:space:]]+(.*~[0-9]*|undefined)$' > /dev/null; then # If only the short version is requested, don't bother # running further git commands if $short; then @@ -57,7 +71,7 @@ scm_version() fi # If we are past a tagged commit (like # "v2.6.30-rc5-302-g72357d5"), we pretty print it. - if atag="`git describe 2>/dev/null`"; then + if atag="`git describe $match 2>/dev/null`"; then echo "$atag" | awk -F- '{printf("-%05d-%s", $(NF-1),$(NF))}' # If we don't have a tag at all we print -g{commitish}. |