diff options
Diffstat (limited to 'target/linux/danube/files/drivers/char')
-rw-r--r-- | target/linux/danube/files/drivers/char/danube_led.c | 1378 |
1 files changed, 1378 insertions, 0 deletions
diff --git a/target/linux/danube/files/drivers/char/danube_led.c b/target/linux/danube/files/drivers/char/danube_led.c new file mode 100644 index 000000000..531c7ed0c --- /dev/null +++ b/target/linux/danube/files/drivers/char/danube_led.c @@ -0,0 +1,1378 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Copyright (C) 2006 infineon + * Copyright (C) 2007 John Crispin <blogic@openwrt.org> + * + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#include <asm/unistd.h> +#include <linux/errno.h> + +/* + * Chip Specific Head File + */ +#include <asm/danube/port.h> + +#include <asm/danube/danube_led.h> +#include <asm/danube/danube_gptu.h> + + +/* + * #################################### + * Definition + * #################################### + */ + +#define DEBUG_ON_AMAZON 0 + +#define DATA_CLOCKING_EDGE FALLING_EDGE + +#define BOARD_TYPE REFERENCE_BOARD + +#define DEBUG_WRITE_REGISTER 0 + +#define RISING_EDGE 0 +#define FALLING_EDGE 1 + +#define EVALUATION_BOARD 0 +#define REFERENCE_BOARD 1 + +/* + * GPIO Driver Function Wrapping + */ +#define port_reserve_pin danube_port_reserve_pin +#define port_free_pin danube_port_free_pin +#define port_set_altsel0 danube_port_set_altsel0 +#define port_clear_altsel0 danube_port_clear_altsel0 +#define port_set_altsel1 danube_port_set_altsel1 +#define port_clear_altsel1 danube_port_clear_altsel1 +#define port_set_dir_out danube_port_set_dir_out +#define port_clear_dir_out danube_port_clear_dir_out +#define port_set_open_drain danube_port_set_open_drain +#define port_clear_open_drain danube_port_clear_open_drain + +/* + * GPIO Port Used By LED + */ +#define LED_SH_PORT 0 +#define LED_SH_PIN 4 +#define LED_SH_DIR 1 +#define LED_SH_ALTSEL0 1 +#define LED_SH_ALTSEL1 0 +#define LED_SH_OPENDRAIN 1 +#define LED_D_PORT 0 +#define LED_D_PIN 5 +#define LED_D_DIR 1 +#define LED_D_ALTSEL0 1 +#define LED_D_ALTSEL1 0 +#define LED_D_OPENDRAIN 1 +#define LED_ST_PORT 0 +#define LED_ST_PIN 6 +#define LED_ST_DIR 1 +#define LED_ST_ALTSEL0 1 +#define LED_ST_ALTSEL1 0 +#define LED_ST_OPENDRAIN 1 + +#define LED_ADSL0_PORT 0 +#define LED_ADSL0_PIN 4 +#define LED_ADSL0_DIR 1 +#define LED_ADSL0_ALTSEL0 0 +#define LED_ADSL0_ALTSEL1 1 +#define LED_ADSL0_OPENDRAIN 1 +#define LED_ADSL1_PORT 0 +#define LED_ADSL1_PIN 5 +#define LED_ADSL1_DIR 1 +#define LED_ADSL1_ALTSEL0 1 +#define LED_ADSL1_ALTSEL1 1 +#define LED_ADSL1_OPENDRAIN 1 + +#if (LED_SH_PORT == LED_ADSL0_PORT && LED_SH_PIN == LED_ADSL0_PIN) \ + || (LED_D_PORT == LED_ADSL0_PORT && LED_D_PIN == LED_ADSL0_PIN) \ + || (LED_ST_PORT == LED_ADSL0_PORT && LED_ST_PIN == LED_ADSL0_PIN) \ + || (LED_SH_PORT == LED_ADSL1_PORT && LED_SH_PIN == LED_ADSL1_PIN) \ + || (LED_D_PORT == LED_ADSL1_PORT && LED_D_PIN == LED_ADSL1_PIN) \ + || (LED_ST_PORT == LED_ADSL1_PORT && LED_ST_PIN == LED_ADSL1_PIN) + #define ADSL_LED_IS_EXCLUSIVE 1 +#else + #define ADSL_LED_IS_EXCLUSIVE 0 +#endif + +/* + * Define GPIO Functions + */ +#if LED_SH_DIR + #define LED_SH_DIR_SETUP port_set_dir_out +#else + #define LED_SH_DIR_SETUP port_clear_dir_out +#endif +#if LED_SH_ALTSEL0 + #define LED_SH_ALTSEL0_SETUP port_set_altsel0 +#else + #define LED_SH_ALTSEL0_SETUP port_clear_altsel0 +#endif +#if LED_SH_ALTSEL1 + #define LED_SH_ALTSEL1_SETUP port_set_altsel1 +#else + #define LED_SH_ALTSEL1_SETUP port_clear_altsel1 +#endif +#if LED_SH_OPENDRAIN + #define LED_SH_OPENDRAIN_SETUP port_set_open_drain +#else + #define LED_SH_OPENDRAIN_SETUP port_clear_open_drain +#endif + +#if LED_D_DIR + #define LED_D_DIR_SETUP port_set_dir_out +#else + #define LED_D_DIR_SETUP port_clear_dir_out +#endif +#if LED_D_ALTSEL0 + #define LED_D_ALTSEL0_SETUP port_set_altsel0 +#else + #define LED_D_ALTSEL0_SETUP port_clear_altsel0 +#endif +#if LED_D_ALTSEL1 + #define LED_D_ALTSEL1_SETUP port_set_altsel1 +#else + #define LED_D_ALTSEL1_SETUP port_clear_altsel1 +#endif +#if LED_D_OPENDRAIN + #define LED_D_OPENDRAIN_SETUP port_set_open_drain +#else + #define LED_D_OPENDRAIN_SETUP port_clear_open_drain +#endif + +#if LED_ST_DIR + #define LED_ST_DIR_SETUP port_set_dir_out +#else + #define LED_ST_DIR_SETUP port_clear_dir_out +#endif +#if LED_ST_ALTSEL0 + #define LED_ST_ALTSEL0_SETUP port_set_altsel0 +#else + #define LED_ST_ALTSEL0_SETUP port_clear_altsel0 +#endif +#if LED_ST_ALTSEL1 + #define LED_ST_ALTSEL1_SETUP port_set_altsel1 +#else + #define LED_ST_ALTSEL1_SETUP port_clear_altsel1 +#endif +#if LED_ST_OPENDRAIN + #define LED_ST_OPENDRAIN_SETUP port_set_open_drain +#else + #define LED_ST_OPENDRAIN_SETUP port_clear_open_drain +#endif + +#if LED_ADSL0_DIR + #define LED_ADSL0_DIR_SETUP port_set_dir_out +#else + #define LED_ADSL0_DIR_SETUP port_clear_dir_out +#endif +#if LED_ADSL0_ALTSEL0 + #define LED_ADSL0_ALTSEL0_SETUP port_set_altsel0 +#else + #define LED_ADSL0_ALTSEL0_SETUP port_clear_altsel0 +#endif +#if LED_ADSL0_ALTSEL1 + #define LED_ADSL0_ALTSEL1_SETUP port_set_altsel1 +#else + #define LED_ADSL0_ALTSEL1_SETUP port_clear_altsel1 +#endif +#if LED_ADSL0_OPENDRAIN + #define LED_ADSL0_OPENDRAIN_SETUP port_set_open_drain +#else + #define LED_ADSL0_OPENDRAIN_SETUP port_clear_open_drain +#endif + +#if LED_ADSL1_DIR + #define LED_ADSL1_DIR_SETUP port_set_dir_out +#else + #define LED_ADSL1_DIR_SETUP port_clear_dir_out +#endif +#if LED_ADSL1_ALTSEL0 + #define LED_ADSL1_ALTSEL0_SETUP port_set_altsel0 +#else + #define LED_ADSL1_ALTSEL0_SETUP port_clear_altsel0 +#endif +#if LED_ADSL1_ALTSEL1 + #define LED_ADSL1_ALTSEL1_SETUP port_set_altsel1 +#else + #define LED_ADSL1_ALTSEL1_SETUP port_clear_altsel1 +#endif +#if LED_ADSL1_OPENDRAIN + #define LED_ADSL1_OPENDRAIN_SETUP port_set_open_drain +#else + #define LED_ADSL1_OPENDRAIN_SETUP port_clear_open_drain +#endif + +/* + * LED Device Minor Number + */ +#if !defined(LED_MINOR) + #define LED_MINOR 151 // This number is written in Linux kernel document "devices.txt" +#endif // !defined(LED_MINOR) + +/* + * Bits Operation + */ +#define GET_BITS(x, msb, lsb) (((x) & ((1 << ((msb) + 1)) - 1)) >> (lsb)) +#define SET_BITS(x, msb, lsb, value) (((x) & ~(((1 << ((msb) + 1)) - 1) ^ ((1 << (lsb)) - 1))) | (((value) & ((1 << (1 + (msb) - (lsb))) - 1)) << (lsb))) + +/* + * LED Registers Mapping + */ +#define DANUBE_LED (KSEG1 + 0x1E100BB0) +#define DANUBE_LED_CON0 ((volatile u32*)(DANUBE_LED + 0x0000)) +#define DANUBE_LED_CON1 ((volatile u32*)(DANUBE_LED + 0x0004)) +#define DANUBE_LED_CPU0 ((volatile u32*)(DANUBE_LED + 0x0008)) +#define DANUBE_LED_CPU1 ((volatile u32*)(DANUBE_LED + 0x000C)) +#define DANUBE_LED_AR ((volatile u32*)(DANUBE_LED + 0x0010)) + +/* + * LED Control 0 Register + */ +#define LED_CON0_SWU (*DANUBE_LED_CON0 & (1 << 31)) +#define LED_CON0_FALLING_EDGE (*DANUBE_LED_CON0 & (1 << 26)) +#define LED_CON0_AD1 (*DANUBE_LED_CON0 & (1 << 25)) +#define LED_CON0_AD0 (*DANUBE_LED_CON0 & (1 << 24)) +#define LED_CON0_LBn(n) (*DANUBE_LED_CON0 & (1 << n)) +#define LED_CON0_DEFAULT_VALUE (0x80000000 | (DATA_CLOCKING_EDGE << 26)) + +/* + * LED Control 1 Register + */ +#define LED_CON1_US (*DANUBE_LED_CON1 >> 30) +#define LED_CON1_SCS (*DANUBE_LED_CON1 & (1 << 28)) +#define LED_CON1_FPID GET_BITS(*DANUBE_LED_CON1, 27, 23) +#define LED_CON1_FPIS GET_BITS(*DANUBE_LED_CON1, 21, 20) +#define LED_CON1_DO GET_BITS(*DANUBE_LED_CON1, 19, 18) +#define LED_CON1_G2 (*DANUBE_LED_CON1 & (1 << 2)) +#define LED_CON1_G1 (*DANUBE_LED_CON1 & (1 << 1)) +#define LED_CON1_G0 (*DANUBE_LED_CON1 & 0x01) +#define LED_CON1_G (*DANUBE_LED_CON1 & 0x07) +#define LED_CON1_DEFAULT_VALUE 0x00000000 + +/* + * LED Data Output CPU 0 Register + */ +#define LED_CPU0_Ln(n) (*DANUBE_LED_CPU0 & (1 << n)) +#define LED_LED_CPU0_DEFAULT_VALUE 0x00000000 + +/* + * LED Data Output CPU 1 Register + */ +#define LED_CPU1_Ln(n) (*DANUBE_LED_CPU1 & (1 << n)) +#define LED_LED_CPU1_DEFAULT_VALUE 0x00000000 + +/* + * LED Data Output Access Rights Register + */ +#define LED_AR_Ln(n) (*DANUBE_LED_AR & (1 << n)) +#define LED_AR_DEFAULT_VALUE 0x00000000 + + +/* + * #################################### + * Preparation of Debug on Amazon Chip + * #################################### + */ + +/* + * If try module on Amazon chip, prepare some tricks to prevent invalid memory write. + */ +#if defined(DEBUG_ON_AMAZON) && DEBUG_ON_AMAZON + char g_pFakeRegisters[0x50]; + + #undef DEBUG_WRITE_REGISTER + + #undef DANUBE_LED + #define DANUBE_LED g_pFakeRegisters + + #undef port_reserve_pin + #undef port_free_pin + #undef port_set_altsel0 + #undef port_clear_altsel0 + #undef port_set_altsel1 + #undef port_clear_altsel1 + #undef port_set_dir_out + + #define port_reserve_pin amazon_port_reserve_pin + #define port_free_pin amazon_port_free_pin + #define port_set_altsel0 amazon_port_set_altsel0 + #define port_clear_altsel0 amazon_port_clear_altsel0 + #define port_set_altsel1 amazon_port_set_altsel1 + #define port_clear_altsel1 amazon_port_clear_altsel1 + #define port_set_dir_out amazon_port_set_dir_out +#endif // defined(DEBUG_ON_AMAZON) && DEBUG_ON_AMAZON + + +/* + * #################################### + * Declaration + * #################################### + */ + +/* + * File Operations + */ +static int led_ioctl(struct inode *, struct file *, unsigned int, unsigned long); +static int led_open(struct inode *, struct file *); +static int led_release(struct inode *, struct file *); + +/* + * Software Update LED + */ +static inline int update_led(void); + +/* + * LED Configuration Functions + */ +static inline u32 set_update_source(u32, unsigned long, unsigned long); +static inline u32 set_blink_in_batch(u32, unsigned long, unsigned long); +static inline u32 set_data_clock_edge(u32, unsigned long); +static inline u32 set_update_clock(u32, unsigned long, unsigned long); +static inline u32 set_store_mode(u32, unsigned long); +static inline u32 set_shift_clock(u32, unsigned long); +static inline u32 set_data_offset(u32, unsigned long); +static inline u32 set_number_of_enabled_led(u32, unsigned long); +static inline u32 set_data_in_batch(u32, unsigned long, unsigned long); +static inline u32 set_access_right(u32, unsigned long, unsigned long); + +/* + * PMU Operation + */ +static inline void enable_led(void); +static inline void disable_led(void); + +/* + * GPIO Setup & Release + */ +static inline int setup_gpio_port(unsigned long); +static inline void release_gpio_port(unsigned long); + +/* + * GPT Setup & Release + */ +static inline int setup_gpt(int, unsigned long); +static inline void release_gpt(int); + +/* + * Turn On/Off LED + */ +static inline int turn_on_led(unsigned long); +static inline void turn_off_led(unsigned long); + + +/* + * #################################### + * Local Variable + * #################################### + */ + +static struct semaphore led_sem; + +static struct file_operations led_fops = { + owner: THIS_MODULE, + ioctl: led_ioctl, + open: led_open, + release: led_release +}; + +static struct miscdevice led_miscdev = { + LED_MINOR, + "led", + &led_fops, + NULL, + NULL, + NULL +}; + +static unsigned long gpt_on = 0; +static unsigned long gpt_freq = 0; + +static unsigned long adsl_on = 0; +static unsigned long f_led_on = 0; + +static int module_id; + + +/* + * #################################### + * Global Variable + * #################################### + */ + + +/* + * #################################### + * Local Function + * #################################### + */ + +static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret = -EINVAL; + struct led_config_param param; + + switch ( cmd ) + { + case LED_CONFIG: + copy_from_user(¶m, (char*)arg, sizeof(param)); + ret = danube_led_config(¶m); + break; + } + + return ret; +} + +static int led_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int led_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/* + * Description: + * Update LEDs with data stored in register. + * Input: + * none + * Output: + * int --- 0: Success + * else: Error Code + */ +static inline int update_led(void) +{ + int i, j; + + /* + * GPT2 or FPID is the clock to update LEDs automatically. + */ + if ( LED_CON1_US != 0 ) + return 0; + + /* + * Check the status to prevent conflict of two consecutive update + */ + for ( i = 100000; i != 0; i -= j / 16 ) + { + down(&led_sem); + if ( !LED_CON0_SWU ) + { + *DANUBE_LED_CON0 |= 1 << 31; + up(&led_sem); + return 0; + } + else + up(&led_sem); + for ( j = 0; j < 1000 * 16; j++ ); + } + + return -EBUSY; +} + +/* + * Description: + * Select update source for LED bit 0 and bit 1. + * Input: + * reg --- u32, the original register value going to be modified. + * led --- unsigned long, bit 0 stands for LED 0, and bit 1 stands for + * LED 1. If the bit is set, the source value is valid, else + * the source value is invalid. + * source --- unsigned long, bit 0 stands for LED 0, and bit 1 stands for + * LED 1. If the corresponding is cleared, LED is updated with + * value in data register, else LED is updated with ARC module. + * Output: + * u32 --- The updated register value. + */ +static inline u32 set_update_source(u32 reg, unsigned long led, unsigned long source) +{ + return (reg & ~((led & 0x03) << 24)) | ((source & 0x03) << 24); +} + +/* + * Description: + * Define which of the LEDs should change their value based on the US pulse. + * Input: + * reg --- u32, the original register value going to be modified. + * mask --- unsigned long, if the corresponding bit is set, the blink value + * is valid, else the blink value is invalid. + * blink --- unsigned long, if the corresponding bit is set, the LED should + * change its value based on the US pulse. + * Output: + * u32 --- The updated register value. + */ +static inline u32 set_blink_in_batch(u32 reg, unsigned long mask, unsigned long blink) +{ + return (reg & (~(mask & 0x00FFFFFF) & 0x87FFFFFF)) | (blink & 0x00FFFFFF); +} + +static inline u32 set_data_clock_edge(u32 reg, unsigned long f_on_rising_edge) +{ + return f_on_rising_edge ? (reg & ~(1 << 26)) : (reg | (1 << 26)); +} + +/* + * Description: + * Select the clock source for US pulse. + * Input: + * reg --- u32, the original register value going to be modified. + * clock --- unsigned long, there 3 available values: + * 0x00 - use software update bit (SWU) as source. + * 0x01 - use GPT2 as clock source. + * 0x02 - use FPI as clock source. + * fpid --- unsigned long, if FPI is selected as clock source, this field + * specify the divider. Please refer to specification for detail + * description. + * Output: + * u32 --- The updated register value. + */ +static inline u32 set_update_clock(u32 reg, unsigned long clock, unsigned long fpid) +{ + switch ( clock ) + { + case 0: reg &= ~0xC0000000; break; + case 1: reg = (reg & ~0xC0000000) | 0x40000000; break; + case 2: reg = (reg & ~0xCF800000) | 0x80000000 | ((fpid & 0x1F) << 23); break; + } + return reg; +} + +/* + * Description: + * Set the behavior of the LED_ST (shift register) signal. + * Input: + * reg --- u32, the original register value going to be modified. + * mode --- unsigned long, there 2 available values: + * zero - LED controller generate single pulse. + * non-zero - LED controller generate inverted shift clock. + * Output: + * u32 --- The updated register value. + */ +static inline u32 set_store_mode(u32 reg, unsigned long mode) +{ + return mode ? (reg | (1 << 28)) : (reg & ~(1 << 28)); +} + +/* + * Description: + * Select the clock source for shift clock LED_SH. + * Input: + * reg --- u32, the original register value going to be modified. + * fpis --- unsigned long, if FPI is selected as clock source, this field + * specify the divider. Please refer to specification for detail + * description. + * Output: + * u32 --- The updated register value. + */ +static inline u32 set_shift_clock(u32 reg, unsigned long fpis) +{ + return SET_BITS(reg, 21, 20, fpis); +} + +/* + * Description: + * Set the clock cycle offset before data is transmitted to LED_D pin. + * Input: + * reg --- u32, the original register value going to be modified. + * offset --- unsigned long, the number of clock cycles would be inserted + * before data is transmitted to LED_D pin. Zero means no cycle + * inserted. + * Output: + * u32 --- The updated register value. + */ +static inline u32 set_data_offset(u32 reg, unsigned long offset) +{ + return SET_BITS(reg, 19, 18, offset); +} + +/* + * Description: + * Enable or disable LEDs. + * Input: + * reg --- u32, the original register value going to be modified. + * number --- unsigned long, the number of LED to be enabled. This field + * could 0, 8, 16 or 24. Zero means disable all LEDs. + * Output: + * u32 --- The updated register value. + */ +static inline u32 set_number_of_enabled_led(u32 reg, unsigned long number) +{ + u32 bit_mask; + + bit_mask = number > 16 ? 0x07 : (number > 8 ? 0x03 : (number ? 0x01 : 0x00)); + return (reg & ~0x07) | bit_mask; +} + +/* + * Description: + * Turn on/off LEDs. + * Input: + * reg --- u32, the original register value going to be modified. + * mask --- unsigned long, if the corresponding bit is set, the data value + * is valid, else the data value is invalid. + * data --- unsigned long, if the corresponding bit is set, the LED should + * be on, else be off. + * Output: + * u32 --- The updated register value. + */ +static inline u32 set_data_in_batch(u32 reg, unsigned long mask, unsigned long data) +{ + return (reg & ~(mask & 0x00FFFFFF)) | (data & 0x00FFFFFF); +} + +static inline u32 set_access_right(u32 reg, unsigned long mask, unsigned long ar) +{ + return (reg & ~(mask & 0x00FFFFFF)) | (~ar & mask); +} + +/* + * Description: + * Enable LED control module. + * Input: + * none + * Output: + * none + */ +static inline void enable_led(void) +{ +#if !defined(DEBUG_ON_AMAZON) || !DEBUG_ON_AMAZON + /* Activate LED module in PMU. */ + int i = 1000000; + + *(unsigned long *)0xBF10201C &= ~(1 << 11); + while ( --i && (*(unsigned long *)0xBF102020 & (1 << 11)) ); + if ( !i ) + panic("Activating LED in PMU failed!"); +#endif +} + +/* + * Description: + * Disable LED control module. + * Input: + * none + * Output: + * none + */ +static inline void disable_led(void) +{ +#if !defined(DEBUG_ON_AMAZON) || !DEBUG_ON_AMAZON + /* Inactivating LED module in PMU. */ + *(unsigned long *)0xBF10201C |= 1 << 11; +#endif +} + +/* + * Description: + * If LEDs are enabled, GPIO must be setup to enable LED pins. + * Input: + * none + * Output: + * int --- 0: Success + * else: Error Code + */ +static inline int setup_gpio_port(unsigned long adsl) +{ +#if !defined(DEBUG_ON_AMAZON) || !DEBUG_ON_AMAZON + int ret = 0; + + #if defined(DEBUG_WRITE_REGISTER) && DEBUG_WRITE_REGISTER + if ( adsl ) + { + *(unsigned long *)0xBE100B18 |= 0x30; + *(unsigned long *)0xBE100B1C |= 0x20; + *(unsigned long *)0xBE100B1C &= ~0x10; + *(unsigned long *)0xBE100B20 |= 0x30; + *(unsigned long *)0xBE100B24 |= 0x30; + } + else + { + *(unsigned long *)0xBE100B18 |= 0x70; + *(unsigned long *)0xBE100B1C |= 0x70; + *(unsigned long *)0xBE100B20 &= ~0x70; + *(unsigned long *)0xBE100B24 |= 0x70; + } + #else + + /* + * Reserve all pins before config them. + */ + if ( adsl ) + { + ret |= port_reserve_pin(LED_ADSL0_PORT, LED_ADSL0_PIN, module_id); + ret |= port_reserve_pin(LED_ADSL1_PORT, LED_ADSL1_PIN, module_id); + } + else + { + ret |= port_reserve_pin(LED_ST_PORT, LED_ST_PIN, module_id); + ret |= port_reserve_pin(LED_D_PORT, LED_D_PIN, module_id); + ret |= port_reserve_pin(LED_SH_PORT, LED_SH_PIN, module_id); + } + if ( ret ) + { + release_gpio_port(adsl); + return ret; // Should be -EBUSY + } + + if ( adsl ) + { + LED_ADSL0_ALTSEL0_SETUP(LED_ADSL0_PORT, LED_ADSL0_PIN, module_id); + LED_ADSL0_ALTSEL1_SETUP(LED_ADSL0_PORT, LED_ADSL0_PIN, module_id); + LED_ADSL0_DIR_SETUP(LED_ADSL0_PORT, LED_ADSL0_PIN, module_id); + LED_ADSL0_OPENDRAIN_SETUP(LED_ADSL0_PORT, LED_ADSL0_PIN, module_id); + + LED_ADSL1_ALTSEL0_SETUP(LED_ADSL1_PORT, LED_ADSL1_PIN, module_id); + LED_ADSL1_ALTSEL1_SETUP(LED_ADSL1_PORT, LED_ADSL1_PIN, module_id); + LED_ADSL1_DIR_SETUP(LED_ADSL1_PORT, LED_ADSL1_PIN, module_id); + LED_ADSL1_OPENDRAIN_SETUP(LED_ADSL1_PORT, LED_ADSL1_PIN, module_id); + } + else + { + /* + * Set LED_ST + * I don't check the return value, because I'm sure the value is valid + * and the pins are reserved already. + */ + LED_ST_ALTSEL0_SETUP(LED_ST_PORT, LED_ST_PIN, module_id); + LED_ST_ALTSEL1_SETUP(LED_ST_PORT, LED_ST_PIN, module_id); + LED_ST_DIR_SETUP(LED_ST_PORT, LED_ST_PIN, module_id); + LED_ST_OPENDRAIN_SETUP(LED_ST_PORT, LED_ST_PIN, module_id); + + /* + * Set LED_D + */ + LED_D_ALTSEL0_SETUP(LED_D_PORT, LED_D_PIN, module_id); + LED_D_ALTSEL1_SETUP(LED_D_PORT, LED_D_PIN, module_id); + LED_D_DIR_SETUP(LED_D_PORT, LED_D_PIN, module_id); + LED_D_OPENDRAIN_SETUP(LED_D_PORT, LED_D_PIN, module_id); + + /* + * Set LED_SH + */ + LED_SH_ALTSEL0_SETUP(LED_SH_PORT, LED_SH_PIN, module_id); + LED_SH_ALTSEL1_SETUP(LED_SH_PORT, LED_SH_PIN, module_id); + LED_SH_DIR_SETUP(LED_SH_PORT, LED_SH_PIN, module_id); + LED_SH_OPENDRAIN_SETUP(LED_SH_PORT, LED_SH_PIN, module_id); + } + #endif +#endif + + return 0; +} + +/* + * Description: + * If LEDs are all disabled, GPIO must be released so that other application + * could reuse it. + * Input: + * none + * Output: + * none + */ +static inline void release_gpio_port(unsigned long adsl) +{ +#if !defined(DEBUG_ON_AMAZON) || !DEBUG_ON_AMAZON + #if !defined(DEBUG_WRITE_REGISTER) || !DEBUG_WRITE_REGISTER + if ( adsl ) + { + port_free_pin(LED_ADSL0_PORT, LED_ADSL0_PIN, module_id); + port_free_pin(LED_ADSL1_PORT, LED_ADSL1_PIN, module_id); + } + else + { + port_free_pin(LED_ST_PORT, LED_ST_PIN, module_id); + port_free_pin(LED_D_PORT, LED_D_PIN, module_id); + port_free_pin(LED_SH_PORT, LED_SH_PIN, module_id); + } + #endif +#endif +} + +/* + * Description: + * If shifter or update select GPT as clock source, this function would be + * invoked to setup corresponding GPT module. + * Attention please, this function is not working since the GPTU driver is + * not ready. + * Input: + * timer --- int, index of timer. + * freq --- unsigned long, frequency of timer (0.001Hz). This value will be + * rounded off to nearest possible value. + * Output: + * int --- 0: Success + * else: Error Code + */ +static inline int setup_gpt(int timer, unsigned long freq) +{ + int ret; + +#if 0 + timer = TIMER(timer, 0); +#else + timer = TIMER(timer, 1); // 2B +#endif + +#if 0 + ret = set_timer(timer, freq, 1, 0, TIMER_FLAG_NO_HANDLE, 0, 0); +#else + ret = request_timer(timer, + TIMER_FLAG_SYNC + | TIMER_FLAG_16BIT + | TIMER_FLAG_INT_SRC + | TIMER_FLAG_CYCLIC | TIMER_FLAG_COUNTER | TIMER_FLAG_DOWN + | TIMER_FLAG_ANY_EDGE + | TIMER_FLAG_NO_HANDLE, + 8000000 / freq, + 0, + 0); + +#endif +// printk("setup_gpt: timer = %d, freq = %d, return = %d\n", timer, freq, ret); + if ( !ret ) + { + ret = start_timer(timer, 0); + if ( ret ) + free_timer(timer); + } + + return ret; +} + +/* + * Description: + * If shifter or update select other clock source, allocated GPT must be + * released so that other application can use it. + * Attention please, this function is not working since the GPTU driver is + * not ready. + * Input: + * none + * Output: + * none + */ +static inline void release_gpt(int timer) +{ +#if 0 + timer = TIMER(timer, 0); +#else + timer = TIMER(timer, 1); +#endif + stop_timer(timer); + free_timer(timer); +} + +static inline int turn_on_led(unsigned long adsl) +{ + int ret; + + ret = setup_gpio_port(adsl); + if ( ret ) + return ret; + + enable_led(); + + return 0; +} + +static inline void turn_off_led(unsigned long adsl) +{ + release_gpio_port(adsl); + disable_led(); +} + + +/* + * #################################### + * Global Function + * #################################### + */ + +/* + * Description: + * Define which of the LEDs should change its value based on the US pulse. + * Input: + * led --- unsigned int, index of the LED to be set. + * blink --- unsigned int, zero means normal mode, and non-zero means blink + * mode. + * Output: + * int --- 0: Success + * else: Error Code + */ +int danube_led_set_blink(unsigned int led, unsigned int blink) +{ + u32 bit_mask; + + if ( led > 23 ) + return -EINVAL; + + bit_mask = 1 << led; + down(&led_sem); + if ( blink ) + *DANUBE_LED_CON0 |= bit_mask; + else + *DANUBE_LED_CON0 &= ~bit_mask; + up(&led_sem); + + return (led == 0 && LED_CON0_AD0) || (led == 1 && LED_CON0_AD1) ? -EINVAL : 0; +} + +/* + * Description: + * Turn on/off LED. + * Input: + * led --- unsigned int, index of the LED to be set. + * data --- unsigned int, zero means off, and non-zero means on. + * Output: + * int --- 0: Success + * else: Error Code + */ +int danube_led_set_data(unsigned int led, unsigned int data) +{ + unsigned long f_update; + u32 bit_mask; + + if ( led > 23 ) + return -EINVAL; + + bit_mask = 1 << led; + down(&led_sem); + if ( data ) + *DANUBE_LED_CPU0 |= bit_mask; + else + *DANUBE_LED_CPU0 &= ~bit_mask; + f_update = !(*DANUBE_LED_AR & bit_mask); + up(&led_sem); + + return f_update ? update_led() : 0; +} + +/* + * Description: + * Config LED controller. + * Input: + * param --- struct led_config_param*, the members are listed below: + * operation_mask - Select operations to be performed + * led - LED to change update source + * source - Corresponding update source + * blink_mask - LEDs to set blink mode + * blink - Set to blink mode or normal mode + * update_clock - Select the source of update clock + * fpid - If FPI is the source of update clock, set the divider + * store_mode - Set clock mode or single pulse mode for store signal + * fpis - If FPI is the source of shift clock, set the divider + * data_offset - Set cycles to be inserted before data is transmitted + * number_of_enabled_led - Total number of LED to be enabled + * data_mask - LEDs to set value + * data - Corresponding value + * mips0_access_mask - LEDs to set access right + * mips0_access; - 1: the corresponding data is output from MIPS0, 0: MIPS1 + * f_data_clock_on_rising - 1: data clock on rising edge, 0: data clock on falling edge + * Output: + * int --- 0: Success + * else: Error Code + */ +int danube_led_config(struct led_config_param* param) +{ + int ret; + u32 reg_con0, reg_con1, reg_cpu0, reg_ar; + u32 clean_reg_con0, clean_reg_con1, clean_reg_cpu0, clean_reg_ar; + u32 f_setup_gpt2; + u32 f_software_update; + u32 new_led_on, new_adsl_on; + + if ( !param ) + return -EINVAL; + + down(&led_sem); + + reg_con0 = *DANUBE_LED_CON0; + reg_con1 = *DANUBE_LED_CON1; + reg_cpu0 = *DANUBE_LED_CPU0; + reg_ar = *DANUBE_LED_AR; + + clean_reg_con0 = 1; + clean_reg_con1 = 1; + clean_reg_cpu0 = 1; + clean_reg_ar = 1; + + f_setup_gpt2 = 0; + + f_software_update = LED_CON0_SWU ? 0 : 1; + + new_led_on = f_led_on; + new_adsl_on = adsl_on; + + /* ADSL or LED */ + if ( (param->operation_mask & CONFIG_OPERATION_UPDATE_SOURCE) ) + { + if ( param->led > 0x03 || param->source > 0x03 ) + goto INVALID_PARAM; + clean_reg_con0 = 0; + reg_con0 = set_update_source(reg_con0, param->led, param->source); +#if 0 // ADSL0,1 is source for bit 0, 1 in shift register + new_adsl_on = param->source; +#endif + } + + /* Blink */ + if ( (param->operation_mask & CONFIG_OPERATION_BLINK) ) + { + if ( (param->blink_mask & 0xFF000000) || (param->blink & 0xFF000000) ) + goto INVALID_PARAM; + clean_reg_con0 = 0; + reg_con0 = set_blink_in_batch(reg_con0, param->blink_mask, param->blink); + } + + /* Edge */ + if ( (param->operation_mask & CONFIG_DATA_CLOCK_EDGE) ) + { + clean_reg_con0 = 0; + reg_con0 = set_data_clock_edge(reg_con0, param->f_data_clock_on_rising); + } + + /* Update Clock */ + if ( (param->operation_mask & CONFIG_OPERATION_UPDATE_CLOCK) ) + { + if ( param->update_clock > 0x02 || (param->update_clock == 0x02 && param->fpid > 0x3) ) + goto INVALID_PARAM; + clean_reg_con1 = 0; + f_software_update = param->update_clock == 0 ? 1 : 0; + if ( param->update_clock == 0x01 ) + f_setup_gpt2 = 1; + reg_con1 = set_update_clock(reg_con1, param->update_clock, param->fpid); + } + + /* Store Mode */ + if ( (param->operation_mask & CONFIG_OPERATION_STORE_MODE) ) + { + clean_reg_con1 = 0; + reg_con1 = set_store_mode(reg_con1, param->store_mode); + } + + /* Shift Clock */ + if ( (param->operation_mask & CONFIG_OPERATION_SHIFT_CLOCK) ) + { + if ( param->fpis > 0x03 ) + goto INVALID_PARAM; + clean_reg_con1 = 0; + reg_con1 = set_shift_clock(reg_con1, param->fpis); + } + + /* Data Offset */ + if ( (param->operation_mask & CONFIG_OPERATION_DATA_OFFSET) ) + { + if ( param->data_offset > 0x03 ) + goto INVALID_PARAM; + clean_reg_con1 = 0; + reg_con1 = set_data_offset(reg_con1, param->data_offset); + } + + /* Number of LED */ + if ( (param->operation_mask & CONFIG_OPERATION_NUMBER_OF_LED) ) + { + if ( param->number_of_enabled_led > 0x24 ) + goto INVALID_PARAM; + + /* + * If there is at lease one LED enabled, the GPIO pin must be setup. + */ + new_led_on = param->number_of_enabled_led ? 1 : 0; + + clean_reg_con1 = 0; + reg_con1 = set_number_of_enabled_led(reg_con1, param->number_of_enabled_led); + } + + /* LED Data */ + if ( (param->operation_mask & CONFIG_OPERATION_DATA) ) + { + if ( (param->data_mask & 0xFF000000) || (param->data & 0xFF000000) ) + goto INVALID_PARAM; + clean_reg_cpu0 = 0; + reg_cpu0 = set_data_in_batch(reg_cpu0, param->data_mask, param->data); + if ( f_software_update ) + { + clean_reg_con0 = 0; + reg_con0 |= 0x80000000; + } + } + + /* Access Right */ + if ( (param->operation_mask & CONFIG_OPERATION_MIPS0_ACCESS) ) + { + if ( (param->mips0_access_mask & 0xFF000000) || (param->mips0_access & 0xFF000000) ) + goto INVALID_PARAM; + clean_reg_ar = 0; + reg_ar = set_access_right(reg_ar, param->mips0_access_mask, param->mips0_access); + } + + /* Setup GPT */ + if ( f_setup_gpt2 && !new_adsl_on ) // If ADSL led is on, GPT is disabled. + { + ret = 0; + + if ( gpt_on ) + { + if ( gpt_freq != param->fpid ) + { + release_gpt(2); + gpt_on = 0; + ret = setup_gpt(2, param->fpid); + } + } + else + ret = setup_gpt(2, param->fpid); + + if ( ret ) + { +#if 1 + printk("Setup GPT error!\n"); +#endif + goto SETUP_GPT_ERROR; + } + else + { +#if 0 + printk("Setup GPT successfully!\n"); +#endif + gpt_on = 1; + } + } + else + if ( gpt_on ) + { + release_gpt(2); + gpt_on = 0; + } + + /* Turn on LED */ + if ( new_adsl_on ) + new_led_on = 1; + if ( !new_led_on || adsl_on != new_adsl_on ) + { + turn_off_led(adsl_on); + f_led_on = 0; + adsl_on = 0; + } + if ( !f_led_on && new_led_on ) + { + ret = turn_on_led(new_adsl_on); + if ( ret ) + { +#if 1 + printk("Setup GPIO error!\n"); +#endif + goto SETUP_GPIO_ERROR; + } + adsl_on = new_adsl_on; + f_led_on = 1; + } + +#if 0 + if ( (reg_con0 & 0x80000000) ) + printk("software update\n"); +#endif + + /* Write Register */ + if ( !f_led_on ) + enable_led(); + if ( !clean_reg_ar ) + *DANUBE_LED_AR = reg_ar; + if ( !clean_reg_cpu0 ) + *DANUBE_LED_CPU0 = reg_cpu0; + if ( !clean_reg_con1 ) + *DANUBE_LED_CON1 = reg_con1; + if ( !clean_reg_con0 ) + *DANUBE_LED_CON0 = reg_con0; + if ( !f_led_on ) + disable_led(); + +#if defined(DEBUG_ON_AMAZON) && DEBUG_ON_AMAZON + *DANUBE_LED_CON0 &= 0x7FFFFFFF; +#endif + +#if 0 + #if !defined(DEBUG_ON_AMAZON) || !DEBUG_ON_AMAZON + printk("*0xBF10201C = 0x%08lX\n", *(unsigned long *)0xBF10201C); + printk("*0xBE100B18 = 0x%08lX\n", *(unsigned long *)0xBE100B18); + printk("*0xBE100B1C = 0x%08lX\n", *(unsigned long *)0xBE100B1C); + printk("*0xBE100B20 = 0x%08lX\n", *(unsigned long *)0xBE100B20); + printk("*0xBE100B24 = 0x%08lX\n", *(unsigned long *)0xBE100B24); + #endif + printk("*DANUBE_LED_CON0 = 0x%08X\n", *DANUBE_LED_CON0); + printk("*DANUBE_LED_CON1 = 0x%08X\n", *DANUBE_LED_CON1); + printk("*DANUBE_LED_CPU0 = 0x%08X\n", *DANUBE_LED_CPU0); + printk("*DANUBE_LED_CPU1 = 0x%08X\n", *DANUBE_LED_CPU1); + printk("*DANUBE_LED_AR = 0x%08X\n", *DANUBE_LED_AR); +#endif + + up(&led_sem); + return 0; + +SETUP_GPIO_ERROR: + release_gpt(2); + gpt_on = 0; +SETUP_GPT_ERROR: + up(&led_sem); + return ret; + +INVALID_PARAM: + up(&led_sem); + return -EINVAL; +} + + +/* + * #################################### + * Init/Cleanup API + * #################################### + */ + +/* + * Description: + * register device + * Input: + * none + * Output: + * 0 --- successful + * else --- failure, usually it is negative value of error code + */ +int __init danube_led_init(void) +{ + int ret; + struct led_config_param param = {0}; + + enable_led(); + + /* + * Set default value to registers to turn off all LED light. + */ + *DANUBE_LED_AR = LED_AR_DEFAULT_VALUE; + *DANUBE_LED_CPU0 = LED_LED_CPU0_DEFAULT_VALUE; + *DANUBE_LED_CPU1 = LED_LED_CPU1_DEFAULT_VALUE; + *DANUBE_LED_CON1 = LED_CON1_DEFAULT_VALUE; + *DANUBE_LED_CON0 = LED_CON0_DEFAULT_VALUE; + +#if defined(DEBUG_ON_AMAZON) && DEBUG_ON_AMAZON + *DANUBE_LED_CON0 &= 0x7FFFFFFF; +#endif + + disable_led(); + + sema_init(&led_sem, 0); + + ret = misc_register(&led_miscdev); + if ( ret == -EBUSY ) + { + led_miscdev.minor = MISC_DYNAMIC_MINOR; + ret = misc_register(&led_miscdev); + } + if ( ret ) + { + printk(KERN_ERR "led: can't misc_register\n"); + return ret; + } + else + printk(KERN_INFO "led: misc_register on minor = %d\n", led_miscdev.minor); + + module_id = THIS_MODULE ? (int)THIS_MODULE : ((MISC_MAJOR << 8) | led_miscdev.minor); + + up(&led_sem); + +#if BOARD_TYPE == REFERENCE_BOARD + /* Add to enable hardware relay */ + /* Map for LED on reference board + WLAN_READ LED11 OUT1 15 + WARNING LED12 OUT2 14 + FXS1_LINK LED13 OUT3 13 + FXS2_LINK LED14 OUT4 12 + FXO_ACT LED15 OUT5 11 + USB_LINK LED16 OUT6 10 + ADSL2_LINK LED19 OUT7 9 + BT_LINK LED17 OUT8 8 + SD_LINK LED20 OUT9 7 + ADSL2_TRAFFIC LED31 OUT16 0 + Map for hardware relay on reference board + USB Power On OUT11 5 + RELAY OUT12 4 + */ + param.operation_mask = CONFIG_OPERATION_NUMBER_OF_LED; + param.number_of_enabled_led = 16; + danube_led_config(¶m); + param.operation_mask = CONFIG_OPERATION_DATA; + param.data_mask = 1 << 4; + param.data = 1 << 4; + danube_led_config(¶m); +#endif + + // by default, update by FSC clock (FPID) + param.operation_mask = CONFIG_OPERATION_UPDATE_CLOCK; + param.update_clock = 2; // FPID + param.fpid = 3; // 10Hz + danube_led_config(¶m); + + // source of LED 0, 1 is ADSL + param.operation_mask = CONFIG_OPERATION_UPDATE_SOURCE; + param.led = 3; // LED 0, 1 + param.source = 3; // ADSL + danube_led_config(¶m); + + // turn on USB + param.operation_mask = CONFIG_OPERATION_DATA; + param.data_mask = 1 << 5; + param.data = 1 << 5; + danube_led_config(¶m); + + return 0; +} + +/* + * Description: + * deregister device + * Input: + * none + * Output: + * none + */ +void __exit danube_led_exit(void) +{ + int ret; + + ret = misc_deregister(&led_miscdev); + if ( ret ) + printk(KERN_ERR "led: can't misc_deregister, get error number %d\n", -ret); + else + printk(KERN_INFO "led: misc_deregister successfully\n"); +} + +EXPORT_SYMBOL(danube_led_set_blink); +EXPORT_SYMBOL(danube_led_set_data); +EXPORT_SYMBOL(danube_led_config); + +module_init(danube_led_init); +module_exit(danube_led_exit); + |